Merge remote-tracking branch 'origin/master' into sites

pull/8453/head
Puru Vijay 3 years ago
commit ec8b7e87b6

@ -2,8 +2,43 @@
## Unreleased ## Unreleased
* Add a11y warnings:
* `aria-activedescendant-has-tabindex`: elements with `aria-activedescendant` need to have a `tabindex` ([#8172](https://github.com/sveltejs/svelte/pull/8172))
* `role-supports-aria-props`: checks that the (implicit) element role supports the given aria attributes ([#8195](https://github.com/sveltejs/svelte/pull/8195))
* Omit a11y warning on `<video>` tags with `aria-hidden="true"` ([#7874](https://github.com/sveltejs/svelte/issues/7874))
* Omit a11y "no child content" warning on elements with `aria-label` ([#8299](https://github.com/sveltejs/svelte/pull/8299))
* Omit a11y warnings on `<svelte:element>` ([#7939](https://github.com/sveltejs/svelte/issues/7939))
* Make `noreferrer` warning less zealous ([#6289](https://github.com/sveltejs/svelte/issues/6289)) * Make `noreferrer` warning less zealous ([#6289](https://github.com/sveltejs/svelte/issues/6289))
* `trusted-types` CSP compatibility for Web Components ([#8134](https://github.com/sveltejs/svelte/issues/8134)) * `trusted-types` CSP compatibility for Web Components ([#8134](https://github.com/sveltejs/svelte/issues/8134))
* Add `data-sveltekit-replacestate` and `data-sveltekit-keepfocus` attribute typings ([#8281](https://github.com/sveltejs/svelte/issues/8281))
* Don't throw when calling `unsubscribe` twice ([#8186](https://github.com/sveltejs/svelte/pull/8186))
* Detect unused empty attribute CSS selectors ([#8042](https://github.com/sveltejs/svelte/issues/8042))
* Simpler output for reactive statements if dependencies are all static ([#7942](https://github.com/sveltejs/svelte/pull/7942))
* Flush remaining `afterUpdate` calls before `onDestroy` ([#7476](https://github.com/sveltejs/svelte/issues/7476))
* Check value equality for `input` types `url` and `search` ([#7027](https://github.com/sveltejs/svelte/issues/7027))
* Compute node dimensions directly before crossfading ([#4111](https://github.com/sveltejs/svelte/issues/4111))
* Add `readonly` method to convert `writable` store to readonly ([#6518](https://github.com/sveltejs/svelte/pull/6518))
* Ensure `bind:offsetHeight` updates initially ([#4233](https://github.com/sveltejs/svelte/issues/4233))
* Better handling of `inert` attribute ([#7500](https://github.com/sveltejs/svelte/issues/7500))
* Clear inputs when `bind:group` to `undefined` ([#8214](https://github.com/sveltejs/svelte/issues/8214))
* Ensure nested arrays can change at the same time ([#8282](https://github.com/sveltejs/svelte/issues/8282))
* Reduce use of template literals in SSR output for better performance ([#7539](https://github.com/sveltejs/svelte/pull/7539))
* Allow assigning to property of const while destructuring ([#7964](https://github.com/sveltejs/svelte/issues/7964))
* Don't set selected options if value is unbound or not passed ([#5644](https://github.com/sveltejs/svelte/issues/5644))
* Ensure `<input>` value persists when swapping elements with spread attributes in an `#each` block ([#7578](https://github.com/sveltejs/svelte/issues/7578))
* Select first enabled option by default when initial value is undefined ([#7041](https://github.com/sveltejs/svelte/issues/7041))
* Fix race condition on `svelte:element` with transitions ([#7948](https://github.com/sveltejs/svelte/issues/7948))
* Optimise `<svelte:element>` output code for static tag and static attribute ([#8161](https://github.com/sveltejs/svelte/pull/8161))
* Decode html entities correctly ([#8026](https://github.com/sveltejs/svelte/issues/8026))
* Handle `{@html}` tags inside `<template>` tags ([#7364](https://github.com/sveltejs/svelte/pull/7364))
* Introduce parameter to allow for horizontal slide transition ([#6182](https://github.com/sveltejs/svelte/issues/6182))
* Add `naturalWidth` and `naturalHeight` bindings ([#7771](https://github.com/sveltejs/svelte/issues/7771))
* make `<!-- svelte-ignore ... -->` work above components ([#8082](https://github.com/sveltejs/svelte/issues/8082))
* add global compound selector validation ([#6272](https://github.com/sveltejs/svelte/issues/6272))
* add `stopImmediatePropagation` event modifier ([#5085](https://github.com/sveltejs/svelte/issues/5085))
* add `readyState` binding for media elements ([#6666](https://github.com/sveltejs/svelte/issues/6666))
* call `<svelte:component>` update to `this` only when it's dirty ([#4129](https://github.com/sveltejs/svelte/issues/4129))
* support exclusively special characters in component filenames ([#7143](https://github.com/sveltejs/svelte/issues/7143))
## 3.55.1 ## 3.55.1

@ -197,6 +197,9 @@ export interface DOMAttributes<T extends EventTarget> {
'on:message'?: MessageEventHandler<T> | undefined | null; 'on:message'?: MessageEventHandler<T> | undefined | null;
'on:messageerror'?: MessageEventHandler<T> | undefined | null; 'on:messageerror'?: MessageEventHandler<T> | undefined | null;
// Document Events
'on:visibilitychange'?: EventHandler<Event, T> | undefined | null;
// Global Events // Global Events
'on:cancel'?: EventHandler<Event, T> | undefined | null; 'on:cancel'?: EventHandler<Event, T> | undefined | null;
'on:close'?: EventHandler<Event, T> | undefined | null; 'on:close'?: EventHandler<Event, T> | undefined | null;
@ -540,10 +543,12 @@ export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, D
'bind:textContent'?: string | undefined | null; 'bind:textContent'?: string | undefined | null;
// SvelteKit // SvelteKit
'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null;
'data-sveltekit-noscroll'?: true | '' | 'off' | undefined | null; 'data-sveltekit-noscroll'?: true | '' | 'off' | undefined | null;
'data-sveltekit-preload-code'?: true | '' | 'eager' | 'viewport' | 'hover' | 'tap' | 'off' | undefined | null; 'data-sveltekit-preload-code'?: true | '' | 'eager' | 'viewport' | 'hover' | 'tap' | 'off' | undefined | null;
'data-sveltekit-preload-data'?: true | '' | 'hover' | 'tap' | 'off' | undefined | null; 'data-sveltekit-preload-data'?: true | '' | 'hover' | 'tap' | 'off' | undefined | null;
'data-sveltekit-reload'?: true | '' | 'off' | undefined | null; 'data-sveltekit-reload'?: true | '' | 'off' | undefined | null;
'data-sveltekit-replacestate'?: true | '' | 'off' | undefined | null;
} }
export type HTMLAttributeAnchorTarget = export type HTMLAttributeAnchorTarget =
@ -705,6 +710,9 @@ export interface HTMLImgAttributes extends HTMLAttributes<HTMLImageElement> {
srcset?: string | undefined | null; srcset?: string | undefined | null;
usemap?: string | undefined | null; usemap?: string | undefined | null;
width?: number | string | undefined | null; width?: number | string | undefined | null;
readonly 'bind:naturalWidth'?: number | undefined | null;
readonly 'bind:naturalHeight'?: number | undefined | null;
} }
export interface HTMLInsAttributes extends HTMLAttributes<HTMLModElement> { export interface HTMLInsAttributes extends HTMLAttributes<HTMLModElement> {
@ -840,6 +848,7 @@ export interface HTMLMediaAttributes<T extends HTMLMediaElement> extends HTMLAtt
*/ */
volume?: number | undefined | null; volume?: number | undefined | null;
readonly 'bind:readyState'?: 0 | 1 | 2 | 3 | 4 | undefined | null;
readonly 'bind:duration'?: number | undefined | null; readonly 'bind:duration'?: number | undefined | null;
readonly 'bind:buffered'?: SvelteMediaTimeRange[] | undefined | null; readonly 'bind:buffered'?: SvelteMediaTimeRange[] | undefined | null;
readonly 'bind:played'?: SvelteMediaTimeRange[] | undefined | null; readonly 'bind:played'?: SvelteMediaTimeRange[] | undefined | null;
@ -860,9 +869,9 @@ export interface HTMLMediaAttributes<T extends HTMLMediaElement> extends HTMLAtt
} }
export interface HTMLMetaAttributes extends HTMLAttributes<HTMLMetaElement> { export interface HTMLMetaAttributes extends HTMLAttributes<HTMLMetaElement> {
charSet?: string | undefined | null; charset?: string | undefined | null;
content?: string | undefined | null; content?: string | undefined | null;
httpequiv?: string | undefined | null; 'http-equiv'?: string | undefined | null;
name?: string | undefined | null; name?: string | undefined | null;
media?: string | undefined | null; media?: string | undefined | null;
} }

1312
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -120,43 +120,43 @@
"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",
"@rollup/plugin-commonjs": "^11.0.0", "@rollup/plugin-commonjs": "^11.0.0",
"@rollup/plugin-json": "^4.0.1", "@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^11.2.1", "@rollup/plugin-node-resolve": "^11.2.1",
"@rollup/plugin-replace": "^2.3.0", "@rollup/plugin-replace": "^5.0.2",
"@rollup/plugin-sucrase": "^3.1.0", "@rollup/plugin-sucrase": "^3.1.0",
"@rollup/plugin-typescript": "^2.0.1", "@rollup/plugin-typescript": "^2.0.1",
"@rollup/plugin-virtual": "^2.0.0", "@rollup/plugin-virtual": "^3.0.1",
"@sveltejs/eslint-config": "github:sveltejs/eslint-config#v5.8.0", "@sveltejs/eslint-config": "github:sveltejs/eslint-config#v5.8.0",
"@types/aria-query": "^5.0.0", "@types/aria-query": "^5.0.0",
"@types/mocha": "^7.0.0", "@types/mocha": "^7.0.0",
"@types/node": "^8.10.53", "@types/node": "^8.10.53",
"@typescript-eslint/eslint-plugin": "^5.22.0", "@typescript-eslint/eslint-plugin": "^5.29.0",
"@typescript-eslint/parser": "^5.22.0", "@typescript-eslint/parser": "^5.29.0",
"acorn": "^8.8.1", "acorn": "^8.8.1",
"agadoo": "^2.0.0", "agadoo": "^3.0.0",
"aria-query": "^5.1.1", "aria-query": "^5.1.1",
"axobject-query": "^3.1.1", "axobject-query": "^3.1.1",
"code-red": "^0.2.5", "code-red": "^1.0.0",
"css-tree": "^2.2.1", "css-tree": "^2.3.1",
"eslint": "^8.26.0", "eslint": "^8.35.0",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.27.0",
"eslint-plugin-svelte3": "^4.0.0", "eslint-plugin-svelte3": "^4.0.0",
"estree-walker": "^3.0.1", "estree-walker": "^3.0.3",
"is-reference": "^3.0.0", "is-reference": "^3.0.1",
"jsdom": "^15.2.1", "jsdom": "^15.2.1",
"kleur": "^4.1.5", "kleur": "^4.1.5",
"locate-character": "^2.0.5", "locate-character": "^2.0.5",
"magic-string": "^0.25.3", "magic-string": "^0.30.0",
"mocha": "^7.0.0", "mocha": "^7.0.0",
"periscopic": "^3.0.4", "periscopic": "^3.1.0",
"puppeteer": "^2.0.0", "puppeteer": "^2.0.0",
"rollup": "^1.27.14", "rollup": "^1.27.14",
"source-map": "^0.7.4", "source-map": "^0.7.4",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"sourcemap-codec": "^1.4.8",
"tiny-glob": "^0.2.9", "tiny-glob": "^0.2.9",
"tslib": "^2.4.1", "tslib": "^2.5.0",
"typescript": "^3.7.5", "typescript": "^3.7.5",
"util": "^0.12.5" "util": "^0.12.5"
} }

@ -206,6 +206,7 @@ Additional conditions can be added with `{:else if expression}`, optionally endi
{/if} {/if}
``` ```
(Blocks don't have to wrap elements, they can also wrap text within elements!)
### {#each ...} ### {#each ...}
@ -538,6 +539,7 @@ The following modifiers are available:
* `preventDefault` — calls `event.preventDefault()` before running the handler * `preventDefault` — calls `event.preventDefault()` before running the handler
* `stopPropagation` — calls `event.stopPropagation()`, preventing the event reaching the next element * `stopPropagation` — calls `event.stopPropagation()`, preventing the event reaching the next element
* `stopImmediatePropagation` - calls `event.stopImmediatePropagation()`, preventing other listeners of the same event from being fired.
* `passive` — improves scrolling performance on touch/wheel events (Svelte will add it automatically where it's safe to do so) * `passive` — improves scrolling performance on touch/wheel events (Svelte will add it automatically where it's safe to do so)
* `nonpassive` — explicitly set `passive: false` * `nonpassive` — explicitly set `passive: false`
* `capture` — fires the handler during the *capture* phase instead of the *bubbling* phase * `capture` — fires the handler during the *capture* phase instead of the *bubbling* phase
@ -712,7 +714,7 @@ Elements with the `contenteditable` attribute support `innerHTML` and `textConte
--- ---
Media elements (`<audio>` and `<video>`) have their own set of bindings — six *readonly* ones... Media elements (`<audio>` and `<video>`) have their own set of bindings — seven *readonly* ones...
* `duration` (readonly) — the total duration of the video, in seconds * `duration` (readonly) — the total duration of the video, in seconds
* `buffered` (readonly) — an array of `{start, end}` objects * `buffered` (readonly) — an array of `{start, end}` objects
@ -720,6 +722,7 @@ Media elements (`<audio>` and `<video>`) have their own set of bindings — six
* `seekable` (readonly) — ditto * `seekable` (readonly) — ditto
* `seeking` (readonly) — boolean * `seeking` (readonly) — boolean
* `ended` (readonly) — boolean * `ended` (readonly) — boolean
* `readyState` (readonly) — number between (and including) 0 and 4
...and five *two-way* bindings: ...and five *two-way* bindings:
@ -740,6 +743,7 @@ Videos additionally have readonly `videoWidth` and `videoHeight` bindings.
bind:seekable bind:seekable
bind:seeking bind:seeking
bind:ended bind:ended
bind:readyState
bind:currentTime bind:currentTime
bind:playbackRate bind:playbackRate
bind:paused bind:paused
@ -750,6 +754,22 @@ Videos additionally have readonly `videoWidth` and `videoHeight` bindings.
></video> ></video>
``` ```
##### Image element bindings
---
Image elements (`<img>`) have two readonly bindings:
* `naturalWidth` (readonly) — the original width of the image, available after the image has loaded
* `naturalHeight` (readonly) — the original height of the image, available after the image has loaded
```sv
<img
bind:naturalWidth
bind:naturalHeight
></img>
```
##### Block-level element bindings ##### Block-level element bindings
--- ---
@ -1500,6 +1520,8 @@ The content is exposed in the child component using the `<slot>` element, which
</Widget> </Widget>
``` ```
Note: If you want to render regular `<slot>` element, You can use `<svelte:element this="slot" />`.
#### `<slot name="`*name*`">` #### `<slot name="`*name*`">`
--- ---

@ -452,6 +452,29 @@ import { get } from 'svelte/store';
const value = get(store); const value = get(store);
``` ```
#### `readonly`
```js
readableStore = readonly(writableStore);
```
---
This simple helper function makes a store readonly. You can still subscribe to the changes from the original one using this new readable store.
```js
import { readonly } from 'svelte/store';
const writableStore = writable(1);
const readableStore = readonly(writableStore);
readableStore.subscribe(console.log);
writableStore.set(2); // console: 2
readableStore.set(2); // ERROR
```
### `svelte/motion` ### `svelte/motion`
@ -698,7 +721,7 @@ out:fly={params}
--- ---
Animates the x and y positions and the opacity of an element. `in` transitions animate from an element's current (default) values to the provided values, passed as parameters. `out` transitions animate from the provided values to an element's default values. Animates the x and y positions and the opacity of an element. `in` transitions animate from the provided values, passed as parameters to the element's default values. `out` transitions animate from the element's default values to the provided values.
`fly` accepts the following parameters: `fly` accepts the following parameters:
@ -745,6 +768,7 @@ Slides an element in and out.
* `delay` (`number`, default 0) — milliseconds before starting * `delay` (`number`, default 0) — milliseconds before starting
* `duration` (`number`, default 400) — milliseconds the transition lasts * `duration` (`number`, default 400) — milliseconds the transition lasts
* `easing` (`function`, default `cubicOut`) — an [easing function](/docs#run-time-svelte-easing) * `easing` (`function`, default `cubicOut`) — an [easing function](/docs#run-time-svelte-easing)
- `axis` (`x` | `y`, default `y`) — the axis of motion along which the transition occurs
```sv ```sv
<script> <script>
@ -753,8 +777,8 @@ Slides an element in and out.
</script> </script>
{#if condition} {#if condition}
<div transition:slide="{{delay: 250, duration: 300, easing: quintOut }}"> <div transition:slide="{{delay: 250, duration: 300, easing: quintOut, axis: 'x'}}">
slides in and out slides in and out horizontally
</div> </div>
{/if} {/if}
``` ```

@ -19,6 +19,18 @@ Enforce no `accesskey` on element. Access keys are HTML attributes that allow we
--- ---
### `a11y-aria-activedescendant-has-tabindex`
An element with `aria-activedescendant` must be tabbable, so it must either have an inherent `tabindex` or declare `tabindex` as an attribute.
```sv
<!-- A11y: Elements with attribute aria-activedescendant should have tabindex value -->
<div aria-activedescendant="some-id" />
```
---
### `a11y-aria-attributes` ### `a11y-aria-attributes`
Certain reserved DOM elements do not support ARIA roles, states and properties. This is often because they are not visible, for example `meta`, `html`, `script`, `style`. This rule enforces that these DOM elements do not contain the `aria-*` props. Certain reserved DOM elements do not support ARIA roles, states and properties. This is often because they are not visible, for example `meta`, `html`, `script`, `style`. This rule enforces that these DOM elements do not contain the `aria-*` props.
@ -43,7 +55,7 @@ Enforce that `autofocus` is not used on elements. Autofocusing elements can caus
### `a11y-click-events-have-key-events` ### `a11y-click-events-have-key-events`
Enforce `on:click` is accompanied by at least one of the following: `onKeyUp`, `onKeyDown`, `onKeyPress`. Coding for the keyboard is important for users with physical disabilities who cannot use a mouse, AT compatibility, and screenreader users. Enforce `on:click` is accompanied by at least one of the following: `on:keyup`, `on:keydown`, `on:keypress`. Coding for the keyboard is important for users with physical disabilities who cannot use a mouse, AT compatibility, and screenreader users.
This does not apply for interactive or hidden elements. This does not apply for interactive or hidden elements.
@ -52,6 +64,8 @@ This does not apply for interactive or hidden elements.
<div on:click={() => {}} /> <div on:click={() => {}} />
``` ```
Note that the `keypress` event is now deprecated, so it is officially recommended to use either the `keyup` or `keydown` event instead, accordingly.
--- ---
### `a11y-distracting-elements` ### `a11y-distracting-elements`
@ -296,6 +310,20 @@ Elements with ARIA roles must have all required attributes for that role.
--- ---
### `a11y-role-supports-aria-props`
Elements with explicit or implicit roles defined contain only `aria-*` properties supported by that role.
```sv
<!-- A11y: The attribute 'aria-multiline' is not supported by the role 'link'. -->
<div role="link" aria-multiline />
<!-- A11y: The attribute 'aria-required' is not supported by the role 'listitem'. This role is implicit on the element <li>. -->
<li aria-required />
```
---
### `a11y-structure` ### `a11y-structure`
Enforce that certain DOM elements have the correct structure. Enforce that certain DOM elements have the correct structure.

@ -4,26 +4,26 @@
let showModal = false; let showModal = false;
</script> </script>
<button on:click="{() => showModal = true}"> <button on:click={() => (showModal = true)}>
show modal show modal
</button> </button>
{#if showModal} <Modal bind:showModal>
<Modal on:close="{() => showModal = false}"> <h2 slot="header">
<h2 slot="header"> modal
modal <small><em>adjective</em> mod·al \ˈmō-dəl\</small>
<small><em>adjective</em> mod·al \ˈmō-dəl\</small> </h2>
</h2>
<ol class="definition-list"> <ol class="definition-list">
<li>of or relating to modality in logic</li> <li>of or relating to modality in logic</li>
<li>containing provisions as to the mode of procedure or the manner of taking effect —used of a contract or legacy</li> <li>
<li>of or relating to a musical mode</li> containing provisions as to the mode of procedure or the manner of taking effect —used of a contract or legacy
<li>of or relating to structure as opposed to substance</li> </li>
<li>of, relating to, or constituting a grammatical form or category characteristically indicating predication</li> <li>of or relating to a musical mode</li>
<li>of or relating to a statistical mode</li> <li>of or relating to structure as opposed to substance</li>
</ol> <li>of, relating to, or constituting a grammatical form or category characteristically indicating predication</li>
<li>of or relating to a statistical mode</li>
</ol>
<a href="https://www.merriam-webster.com/dictionary/modal">merriam-webster.com</a> <a href="https://www.merriam-webster.com/dictionary/modal">merriam-webster.com</a>
</Modal> </Modal>
{/if}

@ -1,80 +1,62 @@
<script> <script>
import { createEventDispatcher, onDestroy } from 'svelte'; export let showModal; // boolean
const dispatch = createEventDispatcher(); let dialog; // HTMLDialogElement
const close = () => dispatch('close');
let modal; $: if (dialog && showModal) dialog.showModal();
const handle_keydown = e => {
if (e.key === 'Escape') {
close();
return;
}
if (e.key === 'Tab') {
// trap focus
const nodes = modal.querySelectorAll('*');
const tabbable = Array.from(nodes).filter(n => n.tabIndex >= 0);
let index = tabbable.indexOf(document.activeElement);
if (index === -1 && e.shiftKey) index = 0;
index += tabbable.length + (e.shiftKey ? -1 : 1);
index %= tabbable.length;
tabbable[index].focus();
e.preventDefault();
}
};
const previously_focused = typeof document !== 'undefined' && document.activeElement;
if (previously_focused) {
onDestroy(() => {
previously_focused.focus();
});
}
</script> </script>
<svelte:window on:keydown={handle_keydown}/> <!-- svelte-ignore a11y-click-events-have-key-events -->
<dialog
<div class="modal-background" on:click={close}></div> bind:this={dialog}
on:close={() => (showModal = false)}
<div class="modal" role="dialog" aria-modal="true" bind:this={modal}> on:click|self={() => dialog.close()}
<slot name="header"></slot> >
<hr> <div on:click|stopPropagation>
<slot></slot> <slot name="header" />
<hr> <hr />
<slot />
<!-- svelte-ignore a11y-autofocus --> <hr />
<button autofocus on:click={close}>close modal</button> <!-- svelte-ignore a11y-autofocus -->
</div> <button autofocus on:click={() => dialog.close()}>close modal</button>
</div>
</dialog>
<style> <style>
.modal-background { dialog {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.3);
}
.modal {
position: absolute;
left: 50%;
top: 50%;
width: calc(100vw - 4em);
max-width: 32em; max-width: 32em;
max-height: calc(100vh - 4em);
overflow: auto;
transform: translate(-50%,-50%);
padding: 1em;
border-radius: 0.2em; border-radius: 0.2em;
background: white; border: none;
padding: 0;
}
dialog::backdrop {
background: rgba(0, 0, 0, 0.3);
}
dialog > div {
padding: 1em;
}
dialog[open] {
animation: zoom 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes zoom {
from {
transform: scale(0.95);
}
to {
transform: scale(1);
}
}
dialog[open]::backdrop {
animation: fade 0.2s ease-out;
}
@keyframes fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
} }
button { button {
display: block; display: block;
} }

@ -2,12 +2,20 @@
question: How do I test Svelte apps? question: How do I test Svelte apps?
--- ---
We recommend trying to separate your view logic from your business logic. Data transformation or cross component state management is best kept outside of Svelte components. You can test those parts like you would test any JavaScript functionality that way. When it comes to testing the components, it is best to test the logic of the component and remember that the Svelte library has its own tests and you do not need to test implementation details provided by Svelte. How your application is structured and where logic is defined will determine the best way to ensure it is properly tested. It is important to note that not all logic belongs within a component - this includes concerns such as data transformation, cross-component state management, and logging, among others. Remember that the Svelte library has its own test suite, so you do not need to write tests to validate implementation details provided by Svelte.
There are a few approaches that people take when testing, but it generally involves compiling the component and mounting it to something and then performing the tests. You essentially need to create a bundle for each component you're testing (since svelte is a compiler and not a normal library) and then mount them. You can mount to a JSDOM instance. Or you can use a real browser powered by a library like Playwright, Puppeteer, WebdriverIO or Cypress. A Svelte application will typically have three different types of tests: Unit, Component, and End-to-End (E2E).
Some resources for getting started with unit testing: *Unit Tests*: Focus on testing business logic in isolation. Often this is validating individual functions and edge cases. By minimizing the surface area of these tests they can be kept lean and fast, and by extracting as much logic as possible from your Svelte components more of your application can be covered using them. When creating a new SvelteKit project, you will be asked whether you would like to setup [Vitest](https://vitest.dev/) for unit testing. There are a number of other test runners that could be used as well.
*Component Tests*: Validating that a Svelte component mounts and interacts as expected throughout its lifecycle requires a tool that provides a Document Object Model (DOM). Components can be compiled (since Svelte is a compiler and not a normal library) and mounted to allow asserting against element structure, listeners, state, and all the other capabilities provided by a Svelte component. Tools for component testing range from an in-memory implementation like jsdom paired with a test runner like [Vitest](https://vitest.dev/) to solutions that leverage an actual browser to provide a visual testing capability such as [Playwright](https://playwright.dev/docs/test-components) or [Cypress](https://www.cypress.io/).
*End-to-End Tests*: To ensure your users are able to interact with your application it is necessary to test it as a whole in a manner as close to production as possible. This is done by writing end-to-end (E2E) tests which load and interact with a deployed version of your application in order to simulate how the user will interact with your application. When creating a new SvelteKit project, you will be asked whether you would like to setup [Playwright](https://playwright.dev/) for end-to-end testing. There are many other E2E test libraries available for use as well.
Some resources for getting started with testing:
- [Svelte Testing Library](https://testing-library.com/docs/svelte-testing-library/example/) - [Svelte Testing Library](https://testing-library.com/docs/svelte-testing-library/example/)
- [Svelte Component Testing in Cypress](https://docs.cypress.io/guides/component-testing/svelte/overview)
- [Example using vitest](https://github.com/vitest-dev/vitest/tree/main/examples/svelte) - [Example using vitest](https://github.com/vitest-dev/vitest/tree/main/examples/svelte)
- [Example using uvu test runner with JSDOM](https://github.com/lukeed/uvu/tree/master/examples/svelte) - [Example using uvu test runner with JSDOM](https://github.com/lukeed/uvu/tree/master/examples/svelte)
- [Component testing in real browser](https://webdriver.io/docs/component-testing/svelte) - [Test Svelte components using Vitest & Playwright](https://davipon.hashnode.dev/test-svelte-component-using-vitest-playwright)
- [Component testing with WebdriverIO](https://webdriver.io/docs/component-testing/svelte)

@ -1,4 +1,6 @@
<script> <script>
import { onDestroy } from 'svelte';
const emojis = { const emojis = {
apple: "🍎", apple: "🍎",
banana: "🍌", banana: "🍌",
@ -12,6 +14,11 @@
// ...but the "emoji" variable is fixed upon initialisation of the component // ...but the "emoji" variable is fixed upon initialisation of the component
const emoji = emojis[name]; const emoji = emojis[name];
// observe in the console which entry is removed
onDestroy(() => {
console.log('thing destroyed: ' + name)
});
</script> </script>
<p> <p>

@ -1,4 +1,6 @@
<script> <script>
import { onDestroy } from 'svelte';
const emojis = { const emojis = {
apple: "🍎", apple: "🍎",
banana: "🍌", banana: "🍌",
@ -12,6 +14,11 @@
// ...but the "emoji" variable is fixed upon initialisation of the component // ...but the "emoji" variable is fixed upon initialisation of the component
const emoji = emojis[name]; const emoji = emojis[name];
// observe in the console which entry is removed
onDestroy(() => {
console.log('thing destroyed: ' + name)
});
</script> </script>
<p> <p>

@ -4,7 +4,7 @@ title: Keyed each blocks
By default, when you modify the value of an `each` block, it will add and remove items at the *end* of the block, and update any values that have changed. That might not be what you want. By default, when you modify the value of an `each` block, it will add and remove items at the *end* of the block, and update any values that have changed. That might not be what you want.
It's easier to show why than to explain. Click the 'Remove first thing' button a few times, and notice what happens: it does not remove the first `<Thing>` component, but rather the *last* DOM node. Then it updates the `name` value in the remaining DOM nodes, but not the emoji. It's easier to show why than to explain. Click to expand the `Console`, then click the 'Remove first thing' button a few times, and notice what happens: it does not remove the first `<Thing>` component, but rather the *last* DOM node. Then it updates the `name` value in the remaining DOM nodes, but not the emoji.
Instead, we'd like to remove only the first `<Thing>` component and its DOM node, and leave the others unaffected. Instead, we'd like to remove only the first `<Thing>` component and its DOM node, and leave the others unaffected.

@ -1,8 +1,7 @@
--- ---
title: Textarea inputs title: Textarea inputs
--- ---
The `<textarea>` element behaves similarly to a text input in Svelte — use `bind:value` to create a two-way binding between the `<textarea>` content and the `value` variable:
The `<textarea>` element behaves similarly to a text input in Svelte — use `bind:value`:
```html ```html
<textarea bind:value={value}></textarea> <textarea bind:value={value}></textarea>

@ -4,21 +4,23 @@
export let todo; export let todo;
let div; let button;
afterUpdate(() => { afterUpdate(() => {
flash(div); flash(button);
}); });
</script> </script>
<!-- the text will flash red whenever <!-- the text will flash red whenever
the `todo` object changes --> the `todo` object changes -->
<div bind:this={div} on:click> <button bind:this={button} type="button" on:click>
{todo.done ? '👍': ''} {todo.text} {todo.done ? '👍': ''} {todo.text}
</div> </button>
<style> <style>
div { button {
all: unset;
display: block;
cursor: pointer; cursor: pointer;
line-height: 1.5; line-height: 1.5;
} }

@ -6,21 +6,23 @@
export let todo; export let todo;
let div; let button;
afterUpdate(() => { afterUpdate(() => {
flash(div); flash(button);
}); });
</script> </script>
<!-- the text will flash red whenever <!-- the text will flash red whenever
the `todo` object changes --> the `todo` object changes -->
<div bind:this={div} on:click> <button bind:this={button} type="button" on:click>
{todo.done ? '👍': ''} {todo.text} {todo.done ? '👍': ''} {todo.text}
</div> </button>
<style> <style>
div { button {
all: unset;
display: block;
cursor: pointer; cursor: pointer;
line-height: 1.5; line-height: 1.5;
} }

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

@ -12,4 +12,4 @@ In Svelte, you do this with the special `{@html ...}` tag:
<p>{@html string}</p> <p>{@html string}</p>
``` ```
> Svelte doesn't perform any sanitization of the expression inside `{@html ...}` before it gets inserted into the DOM. In other words, if you use this feature it's critical that you manually escape HTML that comes from sources you don't trust, otherwise you risk exposing your users to XSS attacks. > **Warning!** Svelte doesn't perform any sanitization of the expression inside `{@html ...}` before it gets inserted into the DOM. In other words, if you use this feature it's **critical** that you manually escape HTML that comes from sources you don't trust, otherwise you risk exposing your users to XSS attacks.

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

@ -38,6 +38,7 @@ import compiler_warnings from './compiler_warnings';
import compiler_errors from './compiler_errors'; import compiler_errors from './compiler_errors';
import { extract_ignores_above_position, extract_svelte_ignore_from_comments } from '../utils/extract_svelte_ignore'; import { extract_ignores_above_position, extract_svelte_ignore_from_comments } from '../utils/extract_svelte_ignore';
import check_enable_sourcemap from './utils/check_enable_sourcemap'; import check_enable_sourcemap from './utils/check_enable_sourcemap';
import is_dynamic from './render_dom/wrappers/shared/is_dynamic';
interface ComponentOptions { interface ComponentOptions {
namespace?: string; namespace?: string;
@ -793,20 +794,31 @@ export default class Component {
} }
let deep = false; let deep = false;
let names: string[] | undefined; let names: string[] = [];
if (node.type === 'AssignmentExpression') { if (node.type === 'AssignmentExpression') {
deep = node.left.type === 'MemberExpression'; if (node.left.type === 'ArrayPattern') {
names = deep walk(node.left, {
? [get_object(node.left).name] enter(node: Node, parent: Node) {
: extract_names(node.left); if (node.type === 'Identifier' &&
parent.type !== 'MemberExpression' &&
(parent.type !== 'AssignmentPattern' || parent.right !== node)) {
names.push(node.name);
}
}
});
} else {
deep = node.left.type === 'MemberExpression';
names = deep
? [get_object(node.left).name]
: extract_names(node.left);
}
} else if (node.type === 'UpdateExpression') { } else if (node.type === 'UpdateExpression') {
deep = node.argument.type === 'MemberExpression'; deep = node.argument.type === 'MemberExpression';
const { name } = get_object(node.argument); const { name } = get_object(node.argument);
names = [name]; names.push(name);
} }
if (names.length > 0) {
if (names) {
names.forEach(name => { names.forEach(name => {
let current_scope = scope; let current_scope = scope;
let declaration; let declaration;
@ -939,7 +951,7 @@ export default class Component {
}); });
} }
warn_on_undefined_store_value_references(node: Node, parent: Node, prop: string, scope: Scope) { warn_on_undefined_store_value_references(node: Node, parent: Node, prop: string | number | symbol, scope: Scope) {
if ( if (
node.type === 'LabeledStatement' && node.type === 'LabeledStatement' &&
node.label.name === '$' && node.label.name === '$' &&
@ -1380,12 +1392,11 @@ export default class Component {
module_dependencies.add(name); module_dependencies.add(name);
} }
} }
const is_writable_or_mutated =
variable && (variable.writable || variable.mutated);
if ( if (
should_add_as_dependency && should_add_as_dependency &&
(!owner || owner === component.instance_scope) && (!owner || owner === component.instance_scope) &&
(name[0] === '$' || is_writable_or_mutated) (name[0] === '$' || variable)
) { ) {
dependencies.add(name); dependencies.add(name);
} }
@ -1409,6 +1420,19 @@ export default class Component {
const { expression } = node.body as ExpressionStatement; const { expression } = node.body as ExpressionStatement;
const declaration = expression && (expression as AssignmentExpression).left; const declaration = expression && (expression as AssignmentExpression).left;
const is_dependency_static = Array.from(dependencies).every(
dependency => dependency !== '$$props' && dependency !== '$$restProps' && !is_dynamic(this.var_lookup.get(dependency))
);
if (is_dependency_static) {
assignees.forEach(assignee => {
const variable = component.var_lookup.get(assignee);
if (variable) {
variable.is_reactive_static = true;
}
});
}
unsorted_reactive_declarations.push({ unsorted_reactive_declarations.push({
assignees, assignees,
dependencies, dependencies,

@ -234,6 +234,10 @@ export default {
code: 'css-invalid-global-selector', code: 'css-invalid-global-selector',
message: ':global(...) must contain a single selector' message: ':global(...) must contain a single selector'
}, },
css_invalid_global_selector_position: {
code: 'css-invalid-global-selector-position',
message: ':global(...) not at the start of a selector sequence should not contain type or universal selectors'
},
css_invalid_selector: (selector: string) => ({ css_invalid_selector: (selector: string) => ({
code: 'css-invalid-selector', code: 'css-invalid-selector',
message: `Invalid selector "${selector}"` message: `Invalid selector "${selector}"`

@ -123,6 +123,17 @@ export default {
code: 'a11y-role-has-required-aria-props', code: 'a11y-role-has-required-aria-props',
message: `A11y: Elements with the ARIA role "${role}" must have the following attributes defined: ${props.map(name => `"${name}"`).join(', ')}` message: `A11y: Elements with the ARIA role "${role}" must have the following attributes defined: ${props.map(name => `"${name}"`).join(', ')}`
}), }),
a11y_role_supports_aria_props: (attribute: string, role: string, is_implicit: boolean, name: string) => {
let message = `The attribute '${attribute}' is not supported by the role '${role}'.`;
if (is_implicit) {
message += ` This role is implicit on the element <${name}>.`;
}
return {
code: 'a11y-role-supports-aria-props',
message: `A11y: ${message}`
};
},
a11y_accesskey: { a11y_accesskey: {
code: 'a11y-accesskey', code: 'a11y-accesskey',
message: 'A11y: Avoid using accesskey' message: 'A11y: Avoid using accesskey'
@ -175,10 +186,10 @@ export default {
code: 'a11y-mouse-events-have-key-events', code: 'a11y-mouse-events-have-key-events',
message: `A11y: on:${event} must be accompanied by on:${accompanied_by}` message: `A11y: on:${event} must be accompanied by on:${accompanied_by}`
}), }),
a11y_click_events_have_key_events: () => ({ a11y_click_events_have_key_events: {
code: 'a11y-click-events-have-key-events', code: 'a11y-click-events-have-key-events',
message: 'A11y: visible, non-interactive elements with an on:click event must be accompanied by an on:keydown, on:keyup, or on:keypress event.' message: 'A11y: visible, non-interactive elements with an on:click event must be accompanied by an on:keydown, on:keyup, or on:keypress event.'
}), },
a11y_missing_content: (name: string) => ({ a11y_missing_content: (name: string) => ({
code: 'a11y-missing-content', code: 'a11y-missing-content',
message: `A11y: <${name}> element should have child content` message: `A11y: <${name}> element should have child content`
@ -187,6 +198,10 @@ export default {
code: 'a11y-no-noninteractive-tabindex', code: 'a11y-no-noninteractive-tabindex',
message: 'A11y: noninteractive element cannot have nonnegative tabIndex value' message: 'A11y: noninteractive element cannot have nonnegative tabIndex value'
}, },
a11y_aria_activedescendant_has_tabindex: {
code: 'a11y-aria-activedescendant-has-tabindex',
message: 'A11y: Elements with attribute aria-activedescendant should have tabindex value'
},
redundant_event_modifier_for_touch: { redundant_event_modifier_for_touch: {
code: 'redundant-event-modifier', code: 'redundant-event-modifier',
message: 'Touch event handlers that don\'t use the \'event\' object are passive by default' message: 'Touch event handlers that don\'t use the \'event\' object are passive by default'

@ -76,7 +76,7 @@ export default class Selector {
this.blocks.forEach((block, i) => { this.blocks.forEach((block, i) => {
if (i > 0) { if (i > 0) {
if (block.start - c > 1) { if (block.start - c > 1) {
code.overwrite(c, block.start, block.combinator.name || ' '); code.update(c, block.start, block.combinator.name || ' ');
} }
} }
@ -112,7 +112,7 @@ export default class Selector {
} }
if (selector.type === 'TypeSelector' && selector.name === '*') { if (selector.type === 'TypeSelector' && selector.name === '*') {
code.overwrite(selector.start, selector.end, attr); code.update(selector.start, selector.end, attr);
} else { } else {
code.appendLeft(selector.end, attr); code.appendLeft(selector.end, attr);
} }
@ -148,6 +148,7 @@ export default class Selector {
} }
this.validate_global_with_multiple_selectors(component); this.validate_global_with_multiple_selectors(component);
this.validate_global_compound_selector(component);
this.validate_invalid_combinator_without_selector(component); this.validate_invalid_combinator_without_selector(component);
} }
@ -179,6 +180,23 @@ export default class Selector {
} }
} }
validate_global_compound_selector(component: Component) {
for (const block of this.blocks) {
for (let index = 0; index < block.selectors.length; index++) {
const selector = block.selectors[index];
if (selector.type === 'PseudoClassSelector' &&
selector.name === 'global' &&
index !== 0 &&
selector.children &&
selector.children.length > 0 &&
!/[.:#\s]/.test(selector.children[0].value[0])
) {
component.error(selector, compiler_errors.css_invalid_global_selector_position);
}
}
}
}
get_amount_class_specificity_increased() { get_amount_class_specificity_increased() {
let count = 0; let count = 0;
for (const block of this.blocks) { for (const block of this.blocks) {
@ -350,7 +368,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
const attr = node.attributes.find((attr: CssNode) => attr.name === name); const attr = node.attributes.find((attr: CssNode) => attr.name === name);
if (!attr) return false; if (!attr) return false;
if (attr.is_true) return operator === null; if (attr.is_true) return operator === null;
if (!expected_value) return true; if (expected_value == null) return true;
if (attr.chunks.length === 1) { if (attr.chunks.length === 1) {
const value = attr.chunks[0]; const value = attr.chunks[0];

@ -35,7 +35,7 @@ function minify_declarations(
declarations.forEach((declaration, i) => { declarations.forEach((declaration, i) => {
const separator = i > 0 ? ';' : ''; const separator = i > 0 ? ';' : '';
if ((declaration.node.start - c) > separator.length) { if ((declaration.node.start - c) > separator.length) {
code.overwrite(c, declaration.node.start, separator); code.update(c, declaration.node.start, separator);
} }
declaration.minify(code); declaration.minify(code);
c = declaration.node.end; c = declaration.node.end;
@ -75,7 +75,7 @@ class Rule {
if (selector.used) { if (selector.used) {
const separator = started ? ',' : ''; const separator = started ? ',' : '';
if ((selector.node.start - c) > separator.length) { if ((selector.node.start - c) > separator.length) {
code.overwrite(c, selector.node.start, separator); code.update(c, selector.node.start, separator);
} }
selector.minify(code); selector.minify(code);
@ -133,7 +133,7 @@ class Declaration {
if (block.type === 'Identifier') { if (block.type === 'Identifier') {
const name = block.name; const name = block.name;
if (keyframes.has(name)) { if (keyframes.has(name)) {
code.overwrite(block.start, block.end, keyframes.get(name)); code.update(block.start, block.end, keyframes.get(name));
} }
} }
}); });
@ -156,7 +156,7 @@ class Declaration {
while (regex_whitespace.test(code.original[start])) start += 1; while (regex_whitespace.test(code.original[start])) start += 1;
if (start - c > 1) { if (start - c > 1) {
code.overwrite(c, start, ':'); code.update(c, start, ':');
} }
} }
} }
@ -204,7 +204,7 @@ class Atrule {
code.remove(c, this.node.block.start); code.remove(c, this.node.block.start);
} else if (this.node.name === 'supports') { } else if (this.node.name === 'supports') {
let c = this.node.start + 9; let c = this.node.start + 9;
if (this.node.prelude.start - c > 1) code.overwrite(c, this.node.prelude.start, ' '); if (this.node.prelude.start - c > 1) code.update(c, this.node.prelude.start, ' ');
this.node.prelude.children.forEach((query: CssNode) => { this.node.prelude.children.forEach((query: CssNode) => {
// TODO minify queries // TODO minify queries
c = query.end; c = query.end;
@ -213,7 +213,7 @@ class Atrule {
} else { } else {
let c = this.node.start + this.node.name.length + 1; let c = this.node.start + this.node.name.length + 1;
if (this.node.prelude) { if (this.node.prelude) {
if (this.node.prelude.start - c > 1) code.overwrite(c, this.node.prelude.start, ' '); if (this.node.prelude.start - c > 1) code.update(c, this.node.prelude.start, ' ');
c = this.node.prelude.end; c = this.node.prelude.end;
} }
if (this.node.block && this.node.block.start - c > 0) { if (this.node.block && this.node.block.start - c > 0) {
@ -255,7 +255,7 @@ class Atrule {
}); });
}); });
} else { } else {
code.overwrite(start, end, keyframes.get(name)); code.update(start, end, keyframes.get(name));
} }
} }
}); });

@ -22,7 +22,10 @@ const read_only_media_attributes = new Set([
'seeking', 'seeking',
'ended', 'ended',
'videoHeight', 'videoHeight',
'videoWidth' 'videoWidth',
'naturalWidth',
'naturalHeight',
'readyState'
]); ]);
export default class Binding extends Node { export default class Binding extends Node {

@ -82,11 +82,15 @@ const a11y_nested_implicit_semantics = new Map([
const a11y_implicit_semantics = new Map([ const a11y_implicit_semantics = new Map([
['a', 'link'], ['a', 'link'],
['area', 'link'],
['article', 'article'],
['aside', 'complementary'], ['aside', 'complementary'],
['body', 'document'], ['body', 'document'],
['button', 'button'],
['datalist', 'listbox'], ['datalist', 'listbox'],
['dd', 'definition'], ['dd', 'definition'],
['dfn', 'term'], ['dfn', 'term'],
['dialog', 'dialog'],
['details', 'group'], ['details', 'group'],
['dt', 'term'], ['dt', 'term'],
['fieldset', 'group'], ['fieldset', 'group'],
@ -98,10 +102,14 @@ const a11y_implicit_semantics = new Map([
['h5', 'heading'], ['h5', 'heading'],
['h6', 'heading'], ['h6', 'heading'],
['hr', 'separator'], ['hr', 'separator'],
['img', 'img'],
['li', 'listitem'], ['li', 'listitem'],
['link', 'link'],
['menu', 'list'], ['menu', 'list'],
['meter', 'progressbar'],
['nav', 'navigation'], ['nav', 'navigation'],
['ol', 'list'], ['ol', 'list'],
['option', 'option'],
['optgroup', 'group'], ['optgroup', 'group'],
['output', 'status'], ['output', 'status'],
['progress', 'progressbar'], ['progress', 'progressbar'],
@ -115,11 +123,67 @@ const a11y_implicit_semantics = new Map([
['ul', 'list'] ['ul', 'list']
]); ]);
const menuitem_type_to_implicit_role = new Map([
['command', 'menuitem'],
['checkbox', 'menuitemcheckbox'],
['radio', 'menuitemradio']
]);
const input_type_to_implicit_role = new Map([
['button', 'button'],
['image', 'button'],
['reset', 'button'],
['submit', 'button'],
['checkbox', 'checkbox'],
['radio', 'radio'],
['range', 'slider'],
['number', 'spinbutton'],
['email', 'textbox'],
['search', 'searchbox'],
['tel', 'textbox'],
['text', 'textbox'],
['url', 'textbox']
]);
const combobox_if_list = new Set(['email', 'search', 'tel', 'text', 'url']);
function input_implicit_role(attribute_map: Map<string, Attribute>) {
const type_attribute = attribute_map.get('type');
if (!type_attribute || !type_attribute.is_static) return;
const type = type_attribute.get_static_value() as string;
const list_attribute_exists = attribute_map.has('list');
if (list_attribute_exists && combobox_if_list.has(type)) {
return 'combobox';
}
return input_type_to_implicit_role.get(type);
}
function menuitem_implicit_role(attribute_map: Map<string, Attribute>) {
const type_attribute = attribute_map.get('type');
if (!type_attribute || !type_attribute.is_static) return;
const type = type_attribute.get_static_value() as string;
return menuitem_type_to_implicit_role.get(type);
}
function get_implicit_role(name: string, attribute_map: Map<string, Attribute>) : (string | undefined) {
if (name === 'menuitem') {
return menuitem_implicit_role(attribute_map);
} else if (name === 'input') {
return input_implicit_role(attribute_map);
} else {
return a11y_implicit_semantics.get(name);
}
}
const invisible_elements = new Set(['meta', 'html', 'script', 'style']); const invisible_elements = new Set(['meta', 'html', 'script', 'style']);
const valid_modifiers = new Set([ const valid_modifiers = new Set([
'preventDefault', 'preventDefault',
'stopPropagation', 'stopPropagation',
'stopImmediatePropagation',
'capture', 'capture',
'once', 'once',
'passive', 'passive',
@ -225,6 +289,7 @@ export default class Element extends Node {
namespace: string; namespace: string;
needs_manual_style_scoping: boolean; needs_manual_style_scoping: boolean;
tag_expr: Expression; tag_expr: Expression;
contains_a11y_label: boolean;
get is_dynamic_element() { get is_dynamic_element() {
return this.name === 'svelte:element'; return this.name === 'svelte:element';
@ -484,6 +549,11 @@ export default class Element extends Node {
component.warn(attribute, compiler_warnings.a11y_incorrect_attribute_type(schema, name)); component.warn(attribute, compiler_warnings.a11y_incorrect_attribute_type(schema, name));
} }
} }
// aria-activedescendant-has-tabindex
if (name === 'aria-activedescendant' && !this.is_dynamic_element && !is_interactive_element(this.name, attribute_map) && !attribute_map.has('tabindex')) {
component.warn(attribute, compiler_warnings.a11y_aria_activedescendant_has_tabindex);
}
} }
// aria-role // aria-role
@ -505,7 +575,7 @@ export default class Element extends Node {
} }
// no-redundant-roles // no-redundant-roles
const has_redundant_role = current_role === a11y_implicit_semantics.get(this.name); const has_redundant_role = current_role === get_implicit_role(this.name, attribute_map);
if (this.name === current_role || has_redundant_role) { if (this.name === current_role || has_redundant_role) {
component.warn(attribute, compiler_warnings.a11y_no_redundant_roles(current_role)); component.warn(attribute, compiler_warnings.a11y_no_redundant_roles(current_role));
@ -521,7 +591,7 @@ export default class Element extends Node {
} }
// role-has-required-aria-props // role-has-required-aria-props
if (!is_semantic_role_element(current_role, this.name, attribute_map)) { if (!this.is_dynamic_element && !is_semantic_role_element(current_role, this.name, attribute_map)) {
const role = roles.get(current_role); const role = roles.get(current_role);
if (role) { if (role) {
const required_role_props = Object.keys(role.requiredProps); const required_role_props = Object.keys(role.requiredProps);
@ -553,7 +623,7 @@ export default class Element extends Node {
} }
// scope // scope
if (name === 'scope' && this.name !== 'th') { if (name === 'scope' && !this.is_dynamic_element && this.name !== 'th') {
component.warn(attribute, compiler_warnings.a11y_misplaced_scope); component.warn(attribute, compiler_warnings.a11y_misplaced_scope);
} }
@ -573,6 +643,7 @@ export default class Element extends Node {
const is_non_presentation_role = role?.is_static && !is_presentation_role(role.get_static_value() as ARIARoleDefintionKey); const is_non_presentation_role = role?.is_static && !is_presentation_role(role.get_static_value() as ARIARoleDefintionKey);
if ( if (
!this.is_dynamic_element &&
!is_hidden_from_screen_reader(this.name, attribute_map) && !is_hidden_from_screen_reader(this.name, attribute_map) &&
(!role || is_non_presentation_role) && (!role || is_non_presentation_role) &&
!is_interactive_element(this.name, attribute_map) && !is_interactive_element(this.name, attribute_map) &&
@ -586,19 +657,36 @@ export default class Element extends Node {
if (!has_key_event) { if (!has_key_event) {
component.warn( component.warn(
this, this,
compiler_warnings.a11y_click_events_have_key_events() compiler_warnings.a11y_click_events_have_key_events
); );
} }
} }
} }
// no-noninteractive-tabindex // no-noninteractive-tabindex
if (!is_interactive_element(this.name, attribute_map) && !is_interactive_roles(attribute_map.get('role')?.get_static_value() as ARIARoleDefintionKey)) { if (!this.is_dynamic_element && !is_interactive_element(this.name, attribute_map) && !is_interactive_roles(attribute_map.get('role')?.get_static_value() as ARIARoleDefintionKey)) {
const tab_index = attribute_map.get('tabindex'); const tab_index = attribute_map.get('tabindex');
if (tab_index && (!tab_index.is_static || Number(tab_index.get_static_value()) >= 0)) { if (tab_index && (!tab_index.is_static || Number(tab_index.get_static_value()) >= 0)) {
component.warn(this, compiler_warnings.a11y_no_noninteractive_tabindex); component.warn(this, compiler_warnings.a11y_no_noninteractive_tabindex);
} }
} }
// role-supports-aria-props
const role = attribute_map.get('role');
const role_value = (role ? role.get_static_value() : get_implicit_role(this.name, attribute_map)) as ARIARoleDefintionKey;
if (typeof role_value === 'string' && roles.has(role_value)) {
const { props } = roles.get(role_value);
const invalid_aria_props = new Set(aria.keys().filter(attribute => !(attribute in props)));
const is_implicit = role_value && role === undefined;
attributes
.filter(prop => prop.type !== 'Spread')
.forEach(prop => {
if (invalid_aria_props.has(prop.name as ARIAProperty)) {
component.warn(prop, compiler_warnings.a11y_role_supports_aria_props(prop.name, role_value, is_implicit, this.name));
}
});
}
} }
validate_special_cases() { validate_special_cases() {
@ -620,6 +708,7 @@ export default class Element extends Node {
const id_attribute = attribute_map.get('id'); const id_attribute = attribute_map.get('id');
const name_attribute = attribute_map.get('name'); const name_attribute = attribute_map.get('name');
const target_attribute = attribute_map.get('target'); const target_attribute = attribute_map.get('target');
const aria_label_attribute = attribute_map.get('aria-label');
// links with target="_blank" should have noopener or noreferrer: https://developer.chrome.com/docs/lighthouse/best-practices/external-anchors-use-rel-noopener/ // links with target="_blank" should have noopener or noreferrer: https://developer.chrome.com/docs/lighthouse/best-practices/external-anchors-use-rel-noopener/
// modern browsers add noopener by default, so we only need to check legacy browsers // modern browsers add noopener by default, so we only need to check legacy browsers
@ -642,6 +731,13 @@ export default class Element extends Node {
} }
} }
if (aria_label_attribute) {
const aria_value = aria_label_attribute.get_static_value();
if (aria_value != '') {
this.contains_a11y_label = true;
}
}
if (href_attribute) { if (href_attribute) {
const href_value = href_attribute.get_static_value(); const href_value = href_attribute.get_static_value();
@ -718,7 +814,10 @@ export default class Element extends Node {
} }
if (this.name === 'video') { if (this.name === 'video') {
if (attribute_map.has('muted')) { const aria_hidden_attribute = attribute_map.get('aria-hidden');
const aria_hidden_exist = aria_hidden_attribute && aria_hidden_attribute.get_static_value();
if (attribute_map.has('muted') || aria_hidden_exist === 'true') {
return; return;
} }
@ -881,7 +980,8 @@ export default class Element extends Node {
name === 'muted' || name === 'muted' ||
name === 'playbackRate' || name === 'playbackRate' ||
name === 'seeking' || name === 'seeking' ||
name === 'ended' name === 'ended' ||
name === 'readyState'
) { ) {
if (this.name !== 'audio' && this.name !== 'video') { if (this.name !== 'audio' && this.name !== 'video') {
return component.error(binding, compiler_errors.invalid_binding_element_with('audio> or <video>', name)); return component.error(binding, compiler_errors.invalid_binding_element_with('audio> or <video>', name));
@ -901,6 +1001,13 @@ export default class Element extends Node {
} else if (is_void(this.name)) { } else if (is_void(this.name)) {
return component.error(binding, compiler_errors.invalid_binding_on(binding.name, `void elements like <${this.name}>. Use a wrapper element instead`)); return component.error(binding, compiler_errors.invalid_binding_on(binding.name, `void elements like <${this.name}>. Use a wrapper element instead`));
} }
} else if (
name === 'naturalWidth' ||
name === 'naturalHeight'
) {
if (this.name !== 'img') {
return component.error(binding, compiler_errors.invalid_binding_element_with('<img>', name));
}
} else if ( } else if (
name === 'textContent' || name === 'textContent' ||
name === 'innerHTML' name === 'innerHTML'
@ -922,6 +1029,7 @@ export default class Element extends Node {
validate_content() { validate_content() {
if (!a11y_required_content.has(this.name)) return; if (!a11y_required_content.has(this.name)) return;
if (this.contains_a11y_label) return;
if ( if (
this.bindings this.bindings
.some((binding) => ['textContent', 'innerHTML'].includes(binding.name)) .some((binding) => ['textContent', 'innerHTML'].includes(binding.name))

@ -1,4 +1,4 @@
import Renderer from './Renderer'; import Renderer, { BindingGroup } from './Renderer';
import Wrapper from './wrappers/shared/Wrapper'; import Wrapper from './wrappers/shared/Wrapper';
import { b, x } from 'code-red'; import { b, x } from 'code-red';
import { Node, Identifier, ArrayPattern } from 'estree'; import { Node, Identifier, ArrayPattern } from 'estree';
@ -40,6 +40,7 @@ export default class Block {
bindings: Map<string, Bindings>; bindings: Map<string, Bindings>;
binding_group_initialised: Set<string> = new Set(); binding_group_initialised: Set<string> = new Set();
binding_groups: Set<BindingGroup> = new Set();
chunks: { chunks: {
declarations: Array<Node | Node[]>; declarations: Array<Node | Node[]>;
@ -249,6 +250,7 @@ export default class Block {
} }
} }
this.render_binding_groups();
this.render_listeners(); this.render_listeners();
const properties: Record<string, any> = {}; const properties: Record<string, any> = {};
@ -500,4 +502,10 @@ export default class Block {
} }
} }
} }
render_binding_groups() {
for (const binding_group of this.binding_groups) {
binding_group.render();
}
}
} }

@ -22,6 +22,15 @@ type BitMasks = Array<{
names: string[]; names: string[];
}>; }>;
export interface BindingGroup {
binding_group: (to_reference?: boolean) => Node;
contexts: string[];
list_dependencies: Set<string>;
keypath: string;
elements: Identifier[];
render: () => void;
}
export default class Renderer { export default class Renderer {
component: Component; // TODO Maybe Renderer shouldn't know about Component? component: Component; // TODO Maybe Renderer shouldn't know about Component?
options: CompileOptions; options: CompileOptions;
@ -33,7 +42,7 @@ export default class Renderer {
blocks: Array<Block | Node | Node[]> = []; blocks: Array<Block | Node | Node[]> = [];
readonly: Set<string> = new Set(); readonly: Set<string> = new Set();
meta_bindings: Array<Node | Node[]> = []; // initial values for e.g. window.innerWidth, if there's a <svelte:window> meta tag meta_bindings: Array<Node | Node[]> = []; // initial values for e.g. window.innerWidth, if there's a <svelte:window> meta tag
binding_groups: Map<string, { binding_group: (to_reference?: boolean) => Node; is_context: boolean; contexts: string[]; index: number; keypath: string }> = new Map(); binding_groups: Map<string, BindingGroup> = new Map();
block: Block; block: Block;
fragment: FragmentWrapper; fragment: FragmentWrapper;
@ -64,10 +73,6 @@ export default class Renderer {
this.add_to_context('#slots'); this.add_to_context('#slots');
} }
if (this.binding_groups.size > 0) {
this.add_to_context('$$binding_groups');
}
// main block // main block
this.block = new Block({ this.block = new Block({
renderer: this, renderer: this,

@ -390,13 +390,13 @@ export default function dom(
const resubscribable_reactive_store_unsubscribers = reactive_stores const resubscribable_reactive_store_unsubscribers = reactive_stores
.filter(store => { .filter(store => {
const variable = component.var_lookup.get(store.name.slice(1)); const variable = component.var_lookup.get(store.name.slice(1));
return variable && (variable.reassigned || variable.export_name); return variable && (variable.reassigned || variable.export_name) && !variable.is_reactive_static;
}) })
.map(({ name }) => b`$$self.$$.on_destroy.push(() => ${`$$unsubscribe_${name.slice(1)}`}());`); .map(({ name }) => b`$$self.$$.on_destroy.push(() => ${`$$unsubscribe_${name.slice(1)}`}());`);
if (has_definition) { if (has_definition) {
const reactive_declarations: (Node | Node[]) = []; const reactive_declarations: Node[] = [];
const fixed_reactive_declarations: Node[] = []; // not really 'reactive' but whatever const fixed_reactive_declarations: Array<Node | Node[]> = []; // not really 'reactive' but whatever
component.reactive_declarations.forEach(d => { component.reactive_declarations.forEach(d => {
const dependencies = Array.from(d.dependencies); const dependencies = Array.from(d.dependencies);
@ -417,6 +417,15 @@ export default function dom(
reactive_declarations.push(statement); reactive_declarations.push(statement);
} else { } else {
fixed_reactive_declarations.push(statement); fixed_reactive_declarations.push(statement);
for (const assignee of d.assignees) {
const variable = component.var_lookup.get(assignee);
if (variable && variable.subscribable) {
fixed_reactive_declarations.push(b`
${component.compile_options.dev && b`@validate_store(${assignee}, '${assignee}');`}
@component_subscribe($$self, ${assignee}, $$value => $$invalidate(${renderer.context_lookup.get('$' + assignee).index}, ${'$' + assignee} = $$value));
`);
}
}
} }
}); });
@ -430,7 +439,7 @@ export default function dom(
const name = $name.slice(1); const name = $name.slice(1);
const store = component.var_lookup.get(name); const store = component.var_lookup.get(name);
if (store && (store.reassigned || store.export_name)) { if (store && (store.reassigned || store.export_name) && !store.is_reactive_static) {
const unsubscribe = `$$unsubscribe_${name}`; const unsubscribe = `$$unsubscribe_${name}`;
const subscribe = `$$subscribe_${name}`; const subscribe = `$$subscribe_${name}`;
const i = renderer.context_lookup.get($name).index; const i = renderer.context_lookup.get($name).index;

@ -19,6 +19,7 @@ export function invalidate(renderer: Renderer, scope: Scope, node: Node, names:
!variable.hoistable && !variable.hoistable &&
!variable.global && !variable.global &&
!variable.module && !variable.module &&
!variable.is_reactive_static &&
( (
variable.referenced || variable.referenced ||
variable.subscribable || variable.subscribable ||

@ -448,7 +448,9 @@ export default class EachBlockWrapper extends Wrapper {
block.chunks.mount.push(b` block.chunks.mount.push(b`
for (let #i = 0; #i < ${view_length}; #i += 1) { for (let #i = 0; #i < ${view_length}; #i += 1) {
${iterations}[#i].m(${initial_mount_node}, ${initial_anchor_node}); if (${iterations}[#i]) {
${iterations}[#i].m(${initial_mount_node}, ${initial_anchor_node});
}
} }
`); `);
@ -542,7 +544,9 @@ export default class EachBlockWrapper extends Wrapper {
block.chunks.mount.push(b` block.chunks.mount.push(b`
for (let #i = 0; #i < ${view_length}; #i += 1) { for (let #i = 0; #i < ${view_length}; #i += 1) {
${iterations}[#i].m(${initial_mount_node}, ${initial_anchor_node}); if (${iterations}[#i]) {
${iterations}[#i].m(${initial_mount_node}, ${initial_anchor_node});
}
} }
`); `);

@ -9,7 +9,7 @@ import Text from '../../../nodes/Text';
import handle_select_value_binding from './handle_select_value_binding'; import handle_select_value_binding from './handle_select_value_binding';
import { Identifier, Node } from 'estree'; import { Identifier, Node } from 'estree';
import { namespaces } from '../../../../utils/namespaces'; import { namespaces } from '../../../../utils/namespaces';
import { boolean_attributes } from '../../../../../shared/boolean_attributes'; import { BooleanAttributes, boolean_attributes } from '../../../../../shared/boolean_attributes';
import { regex_double_quotes } from '../../../../utils/patterns'; import { regex_double_quotes } from '../../../../utils/patterns';
const non_textlike_input_types = new Set([ const non_textlike_input_types = new Set([
@ -85,6 +85,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
if (node.name === 'value') { if (node.name === 'value') {
handle_select_value_binding(this, node.dependencies); handle_select_value_binding(this, node.dependencies);
this.parent.has_dynamic_value = true;
} }
} }
@ -180,6 +181,17 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
`; `;
} }
if (this.node.name === 'value' && dependencies.length > 0) {
if (this.parent.bindings.some(binding => binding.node.name === 'group')) {
this.parent.dynamic_value_condition = block.get_unique_name('value_has_changed');
block.add_variable(this.parent.dynamic_value_condition, x`false`);
updater = b`
${updater}
${this.parent.dynamic_value_condition} = true;
`;
}
}
if (dependencies.length > 0) { if (dependencies.length > 0) {
const condition = this.get_dom_update_conditions(block, block.renderer.dirty(dependencies)); const condition = this.get_dom_update_conditions(block, block.renderer.dirty(dependencies));
@ -324,7 +336,8 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
} }
// source: https://html.spec.whatwg.org/multipage/indices.html // source: https://html.spec.whatwg.org/multipage/indices.html
const attribute_lookup = { type AttributeMetadata = { property_name?: string, applies_to?: string[] };
const attribute_lookup: { [key in BooleanAttributes]: AttributeMetadata } & { [key in string]: AttributeMetadata } = {
allowfullscreen: { property_name: 'allowFullscreen', applies_to: ['iframe'] }, allowfullscreen: { property_name: 'allowFullscreen', applies_to: ['iframe'] },
allowpaymentrequest: { property_name: 'allowPaymentRequest', applies_to: ['iframe'] }, allowpaymentrequest: { property_name: 'allowPaymentRequest', applies_to: ['iframe'] },
async: { applies_to: ['script'] }, async: { applies_to: ['script'] },
@ -349,7 +362,9 @@ const attribute_lookup = {
formnovalidate: { property_name: 'formNoValidate', applies_to: ['button', 'input'] }, formnovalidate: { property_name: 'formNoValidate', applies_to: ['button', 'input'] },
hidden: {}, hidden: {},
indeterminate: { applies_to: ['input'] }, indeterminate: { applies_to: ['input'] },
inert: {},
ismap: { property_name: 'isMap', applies_to: ['img'] }, ismap: { property_name: 'isMap', applies_to: ['img'] },
itemscope: {},
loop: { applies_to: ['audio', 'bgsound', 'video'] }, loop: { applies_to: ['audio', 'bgsound', 'video'] },
multiple: { applies_to: ['input', 'select'] }, multiple: { applies_to: ['input', 'select'] },
muted: { applies_to: ['audio', 'video'] }, muted: { applies_to: ['audio', 'video'] },

@ -5,7 +5,7 @@ import InlineComponentWrapper from '../InlineComponent';
import get_object from '../../../utils/get_object'; import get_object from '../../../utils/get_object';
import replace_object from '../../../utils/replace_object'; import replace_object from '../../../utils/replace_object';
import Block from '../../Block'; import Block from '../../Block';
import Renderer from '../../Renderer'; import Renderer, { BindingGroup } from '../../Renderer';
import flatten_reference from '../../../utils/flatten_reference'; import flatten_reference from '../../../utils/flatten_reference';
import { Node, Identifier } from 'estree'; import { Node, Identifier } from 'estree';
import add_to_set from '../../../utils/add_to_set'; import add_to_set from '../../../utils/add_to_set';
@ -26,6 +26,7 @@ export default class BindingWrapper {
snippet: Node; snippet: Node;
is_readonly: boolean; is_readonly: boolean;
needs_lock: boolean; needs_lock: boolean;
binding_group: BindingGroup;
constructor(block: Block, node: Binding, parent: ElementWrapper | InlineComponentWrapper) { constructor(block: Block, node: Binding, parent: ElementWrapper | InlineComponentWrapper) {
this.node = node; this.node = node;
@ -45,6 +46,10 @@ export default class BindingWrapper {
this.object = get_object(this.node.expression.node).name; this.object = get_object(this.node.expression.node).name;
if (this.node.name === 'group') {
this.binding_group = get_binding_group(parent.renderer, this, block);
}
// view to model // view to model
this.handler = get_event_handler(this, parent.renderer, block, this.object, this.node.raw_expression); this.handler = get_event_handler(this, parent.renderer, block, this.object, this.node.raw_expression);
@ -67,6 +72,10 @@ export default class BindingWrapper {
} }
}); });
if (this.binding_group) {
this.binding_group.list_dependencies.forEach(dep => dependencies.add(dep));
}
return dependencies; return dependencies;
} }
@ -105,6 +114,7 @@ export default class BindingWrapper {
const update_conditions: any[] = this.needs_lock ? [x`!${lock}`] : []; const update_conditions: any[] = this.needs_lock ? [x`!${lock}`] : [];
const mount_conditions: any[] = []; const mount_conditions: any[] = [];
let update_or_condition: any = null;
const dependency_array = Array.from(this.get_dependencies()); const dependency_array = Array.from(this.get_dependencies());
@ -120,7 +130,9 @@ export default class BindingWrapper {
type === '' || type === '' ||
type === 'text' || type === 'text' ||
type === 'email' || type === 'email' ||
type === 'password' type === 'password' ||
type === 'search' ||
type === 'url'
) { ) {
update_conditions.push( update_conditions.push(
x`${parent.var}.${this.node.name} !== ${this.snippet}` x`${parent.var}.${this.node.name} !== ${this.snippet}`
@ -140,33 +152,12 @@ export default class BindingWrapper {
switch (this.node.name) { switch (this.node.name) {
case 'group': case 'group':
{ {
const { binding_group, is_context, contexts, index, keypath } = get_binding_group(parent.renderer, this.node, block);
block.renderer.add_to_context('$$binding_groups'); block.renderer.add_to_context('$$binding_groups');
this.binding_group.elements.push(this.parent.var);
if (is_context && !block.binding_group_initialised.has(keypath)) { if ((this.parent as ElementWrapper).has_dynamic_value) {
if (contexts.length > 1) { update_or_condition = (this.parent as ElementWrapper).dynamic_value_condition;
let binding_group = x`${block.renderer.reference('$$binding_groups')}[${index}]`;
for (const name of contexts.slice(0, -1)) {
binding_group = x`${binding_group}[${block.renderer.reference(name)}]`;
block.chunks.init.push(
b`${binding_group} = ${binding_group} || [];`
);
}
}
block.chunks.init.push(
b`${binding_group(true)} = [];`
);
block.binding_group_initialised.add(keypath);
} }
block.chunks.hydrate.push(
b`${binding_group(true)}.push(${parent.var});`
);
block.chunks.destroy.push(
b`${binding_group(true)}.splice(${binding_group(true)}.indexOf(${parent.var}), 1);`
);
break; break;
} }
@ -212,7 +203,8 @@ export default class BindingWrapper {
if (update_dom) { if (update_dom) {
if (update_conditions.length > 0) { if (update_conditions.length > 0) {
const condition = update_conditions.reduce((lhs, rhs) => x`${lhs} && ${rhs}`); let condition = update_conditions.reduce((lhs, rhs) => x`${lhs} && ${rhs}`);
if (update_or_condition) condition = x`${update_or_condition} || (${condition})`;
block.chunks.update.push(b` block.chunks.update.push(b`
if (${condition}) { if (${condition}) {
@ -264,7 +256,7 @@ function get_dom_updater(
const type = node.get_static_attribute_value('type'); const type = node.get_static_attribute_value('type');
const condition = type === 'checkbox' const condition = type === 'checkbox'
? x`~${binding.snippet}.indexOf(${element.var}.__value)` ? x`~(${binding.snippet} || []).indexOf(${element.var}.__value)`
: x`${element.var}.__value === ${binding.snippet}`; : x`${element.var}.__value === ${binding.snippet}`;
return b`${element.var}.checked = ${condition};`; return b`${element.var}.checked = ${condition};`;
@ -277,7 +269,8 @@ function get_dom_updater(
return b`${element.var}.${binding.node.name} = ${binding.snippet};`; return b`${element.var}.${binding.node.name} = ${binding.snippet};`;
} }
function get_binding_group(renderer: Renderer, value: Binding, block: Block) { function get_binding_group(renderer: Renderer, binding: BindingWrapper, block: Block) {
const value = binding.node;
const { parts } = flatten_reference(value.raw_expression); const { parts } = flatten_reference(value.raw_expression);
let keypath = parts.join('.'); let keypath = parts.join('.');
@ -312,41 +305,75 @@ function get_binding_group(renderer: Renderer, value: Binding, block: Block) {
contexts.push(name); contexts.push(name);
} }
// create a global binding_group across blocks
if (!renderer.binding_groups.has(keypath)) { if (!renderer.binding_groups.has(keypath)) {
const index = renderer.binding_groups.size; const index = renderer.binding_groups.size;
// the bind:group depends on the list in the {#each} block as well
// as reordering (removing and adding back to the DOM) may affect the value
const list_dependencies = new Set<string>();
let parent = value.parent;
while (parent) {
if (parent.type === 'EachBlock') {
for (const dep of parent.expression.dynamic_dependencies()) {
list_dependencies.add(dep);
}
}
parent = parent.parent;
}
const elements = [];
contexts.forEach(context => { contexts.forEach(context => {
renderer.add_to_context(context, true); renderer.add_to_context(context, true);
}); });
renderer.binding_groups.set(keypath, { renderer.binding_groups.set(keypath, {
binding_group: (to_reference: boolean = false) => { binding_group: () => {
let binding_group = '$$binding_groups'; let obj = x`$$binding_groups[${index}]`;
let _secondary_indexes = contexts;
if (to_reference) { if (contexts.length > 0) {
binding_group = block.renderer.reference(binding_group); contexts.forEach(secondary_index => {
_secondary_indexes = _secondary_indexes.map(name => block.renderer.reference(name));
}
if (_secondary_indexes.length > 0) {
let obj = x`${binding_group}[${index}]`;
_secondary_indexes.forEach(secondary_index => {
obj = x`${obj}[${secondary_index}]`; obj = x`${obj}[${secondary_index}]`;
}); });
return obj;
} else {
return x`${binding_group}[${index}]`;
} }
return obj;
}, },
is_context: contexts.length > 0,
contexts, contexts,
index, list_dependencies,
keypath keypath,
elements,
render() {
const local_name = block.get_unique_name('binding_group');
const binding_group = block.renderer.reference('$$binding_groups');
block.add_variable(local_name);
if (contexts.length > 0) {
const indexes = { type: 'ArrayExpression', elements: contexts.map(name => block.renderer.reference(name)) };
block.chunks.init.push(
b`${local_name} = @init_binding_group_dynamic(${binding_group}[${index}], ${indexes})`
);
block.chunks.update.push(
b`if (${block.renderer.dirty(Array.from(list_dependencies))}) ${local_name}.u(${indexes})`
);
} else {
block.chunks.init.push(
b`${local_name} = @init_binding_group(${binding_group}[${index}])`
);
}
block.chunks.hydrate.push(
b`${local_name}.p(${elements})`
);
block.chunks.destroy.push(
b`${local_name}.r()`
);
}
}); });
} }
return renderer.binding_groups.get(keypath); // register the binding_group for the block
const binding_group = renderer.binding_groups.get(keypath);
block.binding_groups.add(binding_group);
return binding_group;
} }
function get_event_handler( function get_event_handler(
@ -384,7 +411,7 @@ function get_event_handler(
} }
} }
const value = get_value_from_dom(renderer, binding.parent, binding, block, contextual_dependencies); const value = get_value_from_dom(renderer, binding.parent, binding, contextual_dependencies);
const mutation = b` const mutation = b`
${lhs} = ${value}; ${lhs} = ${value};
@ -400,10 +427,9 @@ function get_event_handler(
} }
function get_value_from_dom( function get_value_from_dom(
renderer: Renderer, _renderer: Renderer,
element: ElementWrapper | InlineComponentWrapper, element: ElementWrapper | InlineComponentWrapper,
binding: BindingWrapper, binding: BindingWrapper,
block: Block,
contextual_dependencies: Set<string> contextual_dependencies: Set<string>
) { ) {
const { node } = element; const { node } = element;
@ -425,7 +451,7 @@ function get_value_from_dom(
// <input type='checkbox' bind:group='foo'> // <input type='checkbox' bind:group='foo'>
if (name === 'group') { if (name === 'group') {
if (type === 'checkbox') { if (type === 'checkbox') {
const { binding_group, contexts } = get_binding_group(renderer, binding.node, block); const { binding_group, contexts } = binding.binding_group;
add_to_set(contextual_dependencies, contexts); add_to_set(contextual_dependencies, contexts);
return x`@get_binding_group_value(${binding_group()}, this.__value, this.checked)`; return x`@get_binding_group_value(${binding_group()}, this.__value, this.checked)`;
} }

@ -41,6 +41,7 @@ export default class EventHandlerWrapper {
if (this.node.modifiers.has('preventDefault')) snippet = x`@prevent_default(${snippet})`; if (this.node.modifiers.has('preventDefault')) snippet = x`@prevent_default(${snippet})`;
if (this.node.modifiers.has('stopPropagation')) snippet = x`@stop_propagation(${snippet})`; if (this.node.modifiers.has('stopPropagation')) snippet = x`@stop_propagation(${snippet})`;
if (this.node.modifiers.has('stopImmediatePropagation')) snippet = x`@stop_immediate_propagation(${snippet})`;
if (this.node.modifiers.has('self')) snippet = x`@self(${snippet})`; if (this.node.modifiers.has('self')) snippet = x`@self(${snippet})`;
if (this.node.modifiers.has('trusted')) snippet = x`@trusted(${snippet})`; if (this.node.modifiers.has('trusted')) snippet = x`@trusted(${snippet})`;
@ -64,6 +65,7 @@ export default class EventHandlerWrapper {
if (block.renderer.options.dev) { if (block.renderer.options.dev) {
args.push(this.node.modifiers.has('preventDefault') ? TRUE : FALSE); args.push(this.node.modifiers.has('preventDefault') ? TRUE : FALSE);
args.push(this.node.modifiers.has('stopPropagation') ? TRUE : FALSE); args.push(this.node.modifiers.has('stopPropagation') ? TRUE : FALSE);
args.push(this.node.modifiers.has('stopImmediatePropagation') ? TRUE : FALSE);
} }
block.event_listeners.push( block.event_listeners.push(

@ -63,13 +63,11 @@ 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'
}, },
{ {
event_names: ['elementresize'], event_names: ['elementresize'],
filter: (_node: Element, name: string) => filter: (_node: Element, name: string) =>
regex_dimensions.test(name) regex_dimensions.test(name)
}, },
// media events // media events
{ {
event_names: ['timeupdate'], event_names: ['timeupdate'],
@ -131,12 +129,23 @@ const events = [
node.is_media_node() && node.is_media_node() &&
(name === 'videoHeight' || name === 'videoWidth') (name === 'videoHeight' || name === 'videoWidth')
}, },
{
// from https://html.spec.whatwg.org/multipage/media.html#ready-states
// and https://html.spec.whatwg.org/multipage/media.html#loading-the-media-resource
event_names: ['loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'playing', 'waiting', 'emptied'],
filter: (node: Element, name: string) =>
node.is_media_node() &&
name === 'readyState'
},
// details event // details event
{ {
event_names: ['toggle'], event_names: ['toggle'],
filter: (node: Element, _name: string) => filter: (node: Element, _name: string) =>
node.name === 'details' node.name === 'details'
},
{
event_names: ['load'],
filter: (_: Element, name: string) => name === 'naturalHeight' || name === 'naturalWidth'
} }
]; ];
@ -151,8 +160,11 @@ export default class ElementWrapper extends Wrapper {
bindings: Binding[]; bindings: Binding[];
event_handlers: EventHandler[]; event_handlers: EventHandler[];
class_dependencies: string[]; class_dependencies: string[];
has_dynamic_attribute: boolean;
select_binding_dependencies?: Set<string>; select_binding_dependencies?: Set<string>;
has_dynamic_value: boolean;
dynamic_value_condition: any;
var: any; var: any;
void: boolean; void: boolean;
@ -219,6 +231,7 @@ export default class ElementWrapper extends Wrapper {
} }
return new AttributeWrapper(this, block, attribute); return new AttributeWrapper(this, block, attribute);
}); });
this.has_dynamic_attribute = !!this.attributes.find(attr => attr.node.get_dependencies().length > 0);
// ordinarily, there'll only be one... but we need to handle // ordinarily, there'll only be one... but we need to handle
// the rare case where an element can have multiple bindings, // the rare case where an element can have multiple bindings,
@ -287,9 +300,8 @@ export default class ElementWrapper extends Wrapper {
(x`#nodes` as unknown) as Identifier (x`#nodes` as unknown) as Identifier
); );
const previous_tag = block.get_unique_name('previous_tag'); const is_tag_dynamic = this.node.tag_expr.dynamic_dependencies().length > 0;
const tag = this.node.tag_expr.manipulate(block); const tag = this.node.tag_expr.manipulate(block);
block.add_variable(previous_tag, tag);
block.chunks.init.push(b` block.chunks.init.push(b`
${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`} ${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`}
@ -311,44 +323,72 @@ export default class ElementWrapper extends Wrapper {
if (${this.var}) ${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'}); if (${this.var}) ${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'});
`); `);
const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); if (is_tag_dynamic) {
const has_transitions = !!(this.node.intro || this.node.outro); const previous_tag = block.get_unique_name('previous_tag');
const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; block.add_variable(previous_tag, tag);
const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes);
block.chunks.update.push(b` const has_transitions = !!(this.node.intro || this.node.outro);
if (${tag}) { const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`;
if (!${previous_tag}) {
${this.var} = ${this.child_dynamic_element_block.name}(#ctx); const tag_will_be_removed = block.get_unique_name('tag_will_be_removed');
${this.var}.c(); if (has_transitions) {
${has_transitions && b`@transition_in(${this.var})`} block.add_variable(tag_will_be_removed, x`false`);
${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); }
} else if (${not_equal}(${previous_tag}, ${tag})) {
${this.var}.d(1); block.chunks.update.push(b`
${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`} if (${tag}) {
${this.renderer.options.dev && this.node.children.length > 0 && b`@validate_void_dynamic_element(${tag});`} if (!${previous_tag}) {
${this.var} = ${this.child_dynamic_element_block.name}(#ctx); ${this.var} = ${this.child_dynamic_element_block.name}(#ctx);
${this.var}.c(); ${previous_tag} = ${tag};
${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); ${this.var}.c();
} else { ${has_transitions && b`@transition_in(${this.var})`}
${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor});
} else if (${not_equal}(${previous_tag}, ${tag})) {
${this.var}.d(1);
${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`}
${this.renderer.options.dev && this.node.children.length > 0 && b`@validate_void_dynamic_element(${tag});`}
${this.var} = ${this.child_dynamic_element_block.name}(#ctx);
${previous_tag} = ${tag};
${this.var}.c();
${has_transitions && b`if (${tag_will_be_removed}) {
${tag_will_be_removed} = false;
@transition_in(${this.var})
}`}
${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor});
} else {
${has_transitions && b`if (${tag_will_be_removed}) {
${tag_will_be_removed} = false;
@transition_in(${this.var})
}`}
${this.var}.p(#ctx, #dirty);
}
} else if (${previous_tag}) {
${has_transitions
? b`
${tag_will_be_removed} = true;
@group_outros();
@transition_out(${this.var}, 1, 1, () => {
${this.var} = null;
${previous_tag} = ${tag};
${tag_will_be_removed} = false;
});
@check_outros();
`
: b`
${this.var}.d(1);
${this.var} = null;
${previous_tag} = ${tag};
`
}
}
`);
} else {
block.chunks.update.push(b`
if (${tag}) {
${this.var}.p(#ctx, #dirty); ${this.var}.p(#ctx, #dirty);
} }
} else if (${previous_tag}) { `);
${has_transitions }
? b`
@group_outros();
@transition_out(${this.var}, 1, 1, () => {
${this.var} = null;
});
@check_outros();
`
: b`
${this.var}.d(1);
${this.var} = null;
`
}
}
${previous_tag} = ${tag};
`);
if (this.child_dynamic_element_block.has_intros) { if (this.child_dynamic_element_block.has_intros) {
block.chunks.intro.push(b`@transition_in(${this.var});`); block.chunks.intro.push(b`@transition_in(${this.var});`);
@ -487,7 +527,11 @@ export default class ElementWrapper extends Wrapper {
block.maintain_context = true; block.maintain_context = true;
} }
this.add_attributes(block); if (this.node.is_dynamic_element) {
this.add_dynamic_element_attributes(block);
} else {
this.add_attributes(block);
}
this.add_directives_in_order(block); this.add_directives_in_order(block);
this.add_transitions(block); this.add_transitions(block);
this.add_animation(block); this.add_animation(block);
@ -765,7 +809,7 @@ export default class ElementWrapper extends Wrapper {
} }
}); });
if (this.node.attributes.some(attr => attr.is_spread) || this.node.is_dynamic_element) { if (this.node.attributes.some(attr => attr.is_spread)) {
this.add_spread_attributes(block); this.add_spread_attributes(block);
return; return;
} }
@ -814,29 +858,18 @@ export default class ElementWrapper extends Wrapper {
} }
`); `);
const fn = this.node.namespace === namespaces.svg ? x`@set_svg_attributes` : x`@set_attributes`; const fn =
this.node.namespace === namespaces.svg
? x`@set_svg_attributes`
: this.node.is_dynamic_element
? x`@set_dynamic_element_data(${this.node.tag_expr.manipulate(block)})`
: x`@set_attributes`;
if (this.node.is_dynamic_element) { block.chunks.hydrate.push(
// call attribute bindings for custom element if tag is custom element b`${fn}(${this.var}, ${data});`
const tag = this.node.tag_expr.manipulate(block); );
const attr_update = this.node.namespace === namespaces.svg
? b`${fn}(${this.var}, ${data});`
: b`
if (/-/.test(${tag})) {
@set_custom_element_data_map(${this.var}, ${data});
} else {
${fn}(${this.var}, ${data});
}`;
block.chunks.hydrate.push(attr_update);
block.chunks.update.push(b`
${data} = @get_spread_update(${levels}, [${updates}]);
${attr_update}`
);
} else {
block.chunks.hydrate.push(
b`${fn}(${this.var}, ${data});`
);
if (this.has_dynamic_attribute) {
block.chunks.update.push(b` block.chunks.update.push(b`
${fn}(${this.var}, ${data} = @get_spread_update(${levels}, [ ${fn}(${this.var}, ${data} = @get_spread_update(${levels}, [
${updates} ${updates}
@ -854,8 +887,9 @@ export default class ElementWrapper extends Wrapper {
} }
block.chunks.mount.push(b` block.chunks.mount.push(b`
(${data}.multiple ? @select_options : @select_option)(${this.var}, ${data}.value); 'value' in ${data} && (${data}.multiple ? @select_options : @select_option)(${this.var}, ${data}.value);
`); `);
block.chunks.update.push(b` block.chunks.update.push(b`
if (${block.renderer.dirty(Array.from(dependencies))} && 'value' in ${data}) (${data}.multiple ? @select_options : @select_option)(${this.var}, ${data}.value); if (${block.renderer.dirty(Array.from(dependencies))} && 'value' in ${data}) (${data}.multiple ? @select_options : @select_option)(${this.var}, ${data}.value);
`); `);
@ -863,7 +897,9 @@ export default class ElementWrapper extends Wrapper {
const type = this.node.get_static_attribute_value('type'); const type = this.node.get_static_attribute_value('type');
if (type === null || type === '' || type === 'text' || type === 'email' || type === 'password') { if (type === null || type === '' || type === 'text' || type === 'email' || type === 'password') {
block.chunks.mount.push(b` block.chunks.mount.push(b`
${this.var}.value = ${data}.value; if ('value' in ${data}) {
${this.var}.value = ${data}.value;
}
`); `);
block.chunks.update.push(b` block.chunks.update.push(b`
if ('value' in ${data}) { if ('value' in ${data}) {
@ -880,6 +916,35 @@ export default class ElementWrapper extends Wrapper {
} }
} }
add_dynamic_element_attributes(block: Block) {
if (this.attributes.length === 0) return;
if (this.has_dynamic_attribute) {
this.add_spread_attributes(block);
return;
}
const static_attributes = [];
this.attributes.forEach((attr) => {
if (attr instanceof SpreadAttributeWrapper) {
static_attributes.push({ type: 'SpreadElement', argument: attr.node.expression.node });
} else {
const name = attr.property_name || attr.name;
static_attributes.push(p`${name}: ${attr.get_value(block)}`);
}
});
const fn =
this.node.namespace === namespaces.svg
? x`@set_svg_attributes`
: this.node.is_dynamic_element
? x`@set_dynamic_element_data(${this.node.tag_expr.manipulate(block)})`
: x`@set_attributes`;
block.chunks.hydrate.push(
b`${fn}(${this.var}, {${static_attributes}});`
);
}
add_transitions(block: Block) { add_transitions(block: Block) {
const { intro, outro } = this.node; const { intro, outro } = this.node;
if (!intro && !outro) return; if (!intro && !outro) return;
@ -1077,7 +1142,7 @@ export default class ElementWrapper extends Wrapper {
block.chunks.hydrate.push(updater); block.chunks.hydrate.push(updater);
if (has_spread || this.node.is_dynamic_element) { if ((this.node.is_dynamic_element || has_spread) && this.has_dynamic_attribute) {
block.chunks.update.push(updater); block.chunks.update.push(updater);
} else if ((dependencies && dependencies.size > 0) || this.class_dependencies.length) { } else if ((dependencies && dependencies.size > 0) || this.class_dependencies.length) {
const all_dependencies = this.class_dependencies.concat(...dependencies); const all_dependencies = this.class_dependencies.concat(...dependencies);

@ -21,6 +21,7 @@ import SlotTemplate from '../../../nodes/SlotTemplate';
import { is_head } from '../shared/is_head'; import { is_head } from '../shared/is_head';
import compiler_warnings from '../../../compiler_warnings'; import compiler_warnings from '../../../compiler_warnings';
import { namespaces } from '../../../../utils/namespaces'; import { namespaces } from '../../../../utils/namespaces';
import { extract_ignores_above_node } from '../../../../utils/extract_svelte_ignore';
type SlotDefinition = { block: Block; scope: TemplateScope; get_context?: Node; get_changes?: Node }; type SlotDefinition = { block: Block; scope: TemplateScope; get_context?: Node; get_changes?: Node };
@ -111,9 +112,12 @@ export default class InlineComponentWrapper extends Wrapper {
return; return;
} }
const ignores = extract_ignores_above_node(this.node);
this.renderer.component.push_ignores(ignores);
if (variable.reassigned || variable.export_name || variable.is_reactive_dependency) { if (variable.reassigned || variable.export_name || variable.is_reactive_dependency) {
this.renderer.component.warn(this.node, compiler_warnings.reactive_component(name)); this.renderer.component.warn(this.node, compiler_warnings.reactive_component(name));
} }
this.renderer.component.pop_ignores();
} }
render( render(
@ -414,6 +418,7 @@ export default class InlineComponentWrapper extends Wrapper {
const switch_props = block.get_unique_name('switch_props'); const switch_props = block.get_unique_name('switch_props');
const snippet = this.node.expression.manipulate(block); const snippet = this.node.expression.manipulate(block);
const dependencies = this.node.expression.dynamic_dependencies();
if (has_css_custom_properties) { if (has_css_custom_properties) {
this.set_css_custom_properties(block, css_custom_properties_wrapper, css_custom_properties_wrapper_element, is_svg_namespace); this.set_css_custom_properties(block, css_custom_properties_wrapper, css_custom_properties_wrapper_element, is_svg_namespace);
@ -464,8 +469,13 @@ export default class InlineComponentWrapper extends Wrapper {
? b`@insert(${tmp_anchor}.parentNode, ${css_custom_properties_wrapper}, ${tmp_anchor});` ? b`@insert(${tmp_anchor}.parentNode, ${css_custom_properties_wrapper}, ${tmp_anchor});`
: b`@insert(${parent_node}, ${css_custom_properties_wrapper}, ${tmp_anchor});`); : b`@insert(${parent_node}, ${css_custom_properties_wrapper}, ${tmp_anchor});`);
let update_condition = x`${switch_value} !== (${switch_value} = ${snippet})`;
if (dependencies.length > 0) {
update_condition = x`${block.renderer.dirty(dependencies)} && ${update_condition}`;
}
block.chunks.update.push(b` block.chunks.update.push(b`
if (${switch_value} !== (${switch_value} = ${snippet})) { if (${update_condition}) {
if (${name}) { if (${name}) {
@group_outros(); @group_outros();
const old_component = ${name}; const old_component = ${name};

@ -16,6 +16,7 @@ import Title from './handlers/Title';
import { AppendTarget, CompileOptions } from '../../interfaces'; import { AppendTarget, CompileOptions } from '../../interfaces';
import { INode } from '../nodes/interfaces'; import { INode } from '../nodes/interfaces';
import { Expression, TemplateLiteral, Identifier } from 'estree'; import { Expression, TemplateLiteral, Identifier } from 'estree';
import { collapse_template_literal } from '../utils/collapse_template_literal';
import { escape_template } from '../utils/stringify'; import { escape_template } from '../utils/stringify';
type Handler = (node: any, renderer: Renderer, options: CompileOptions) => void; type Handler = (node: any, renderer: Renderer, options: CompileOptions) => void;
@ -106,6 +107,9 @@ export default class Renderer {
this.current = last.current; this.current = last.current;
} }
// Optimize the TemplateLiteral to remove unnecessary nodes
collapse_template_literal(popped.literal);
return popped.literal; return popped.literal;
} }

@ -13,4 +13,10 @@ describe('get_name_from_filename', () => {
it('handles Windows filenames', () => { it('handles Windows filenames', () => {
assert.equal(get_name_from_filename('path\\to\\Widget.svelte'), 'Widget'); assert.equal(get_name_from_filename('path\\to\\Widget.svelte'), 'Widget');
}); });
it('handles special characters in filenames', () => {
assert.equal(get_name_from_filename('@.svelte'), '_');
assert.equal(get_name_from_filename('&.svelte'), '_');
assert.equal(get_name_from_filename('~.svelte'), '_');
});
}); });

@ -0,0 +1,34 @@
import { TemplateLiteral } from 'estree';
import { escape_template } from './stringify';
/**
* Collapse string literals together
*/
export function collapse_template_literal(literal: TemplateLiteral) {
if (!literal.quasis.length) return;
const collapsed_quasis = [];
const collapsed_expressions = [];
let cur_quasi = literal.quasis[0];
// An expression always follows a quasi and vice versa, ending with a quasi
for (let i = 0; i < literal.quasis.length; i++) {
const expr = literal.expressions[i];
const next_quasi = literal.quasis[i + 1];
// If an expression is a simple string literal, combine it with its preceding
// and following quasi
if (next_quasi && expr && expr.type === 'Literal' && typeof expr.value === 'string') {
cur_quasi.value.raw += escape_template(expr.value) + next_quasi.value.raw;
} else {
if (expr) {
collapsed_expressions.push(expr);
}
collapsed_quasis.push(cur_quasi);
cur_quasi = next_quasi;
}
}
literal.quasis = collapsed_quasis;
literal.expressions = collapsed_expressions;
}

@ -1,9 +1,8 @@
import { regex_starts_with_underscore, regex_ends_with_underscore } from '../../utils/patterns';
const regex_percentage_characters = /%/g; const regex_percentage_characters = /%/g;
const regex_file_ending = /\.[^.]+$/; const regex_file_ending = /\.[^.]+$/;
const regex_repeated_invalid_variable_identifier_characters = /[^a-zA-Z_$0-9]+/g; const regex_repeated_invalid_variable_identifier_characters = /[^a-zA-Z_$0-9]+/g;
const regex_starts_with_digit = /^(\d)/; const regex_starts_with_digit = /^(\d)/;
const regex_may_starts_or_ends_with_underscore = /^_?(.+?)_?$/;
export default function get_name_from_filename(filename: string) { export default function get_name_from_filename(filename: string) {
if (!filename) return null; if (!filename) return null;
@ -18,12 +17,12 @@ export default function get_name_from_filename(filename: string) {
} }
} }
const base = parts.pop() const base = parts
.pop()
.replace(regex_percentage_characters, 'u') .replace(regex_percentage_characters, 'u')
.replace(regex_file_ending, '') .replace(regex_file_ending, '')
.replace(regex_repeated_invalid_variable_identifier_characters, '_') .replace(regex_repeated_invalid_variable_identifier_characters, '_')
.replace(regex_starts_with_underscore, '') .replace(regex_may_starts_or_ends_with_underscore, '$1')
.replace(regex_ends_with_underscore, '')
.replace(regex_starts_with_digit, '_$1'); .replace(regex_starts_with_digit, '_$1');
if (!base) { if (!base) {

@ -223,6 +223,7 @@ export interface Var {
subscribable?: boolean; subscribable?: boolean;
is_reactive_dependency?: boolean; is_reactive_dependency?: boolean;
imported?: boolean; imported?: boolean;
is_reactive_static?: boolean;
} }
export interface CssResult { export interface CssResult {

@ -132,6 +132,10 @@ export class Parser {
return this.template.slice(this.index, this.index + str.length) === str; return this.template.slice(this.index, this.index + str.length) === str;
} }
/**
* Match a regex at the current index
* @param pattern Should have a ^ anchor at the start so the regex doesn't search past the beginning, resulting in worse performance
*/
match_regex(pattern: RegExp) { match_regex(pattern: RegExp) {
const match = pattern.exec(this.template.slice(this.index)); const match = pattern.exec(this.template.slice(this.index));
if (!match || match.index !== 0) return null; if (!match || match.index !== 0) return null;
@ -148,6 +152,10 @@ export class Parser {
} }
} }
/**
* Search for a regex starting at the current index and return the result if it matches
* @param pattern Should have a ^ anchor at the start so the regex doesn't search past the beginning, resulting in worse performance
*/
read(pattern: RegExp) { read(pattern: RegExp) {
const result = this.match_regex(pattern); const result = this.match_regex(pattern);
if (result) this.index += result.length; if (result) this.index += result.length;

@ -6,6 +6,7 @@ import parser_errors from '../errors';
import { regex_not_newline_characters } from '../../utils/patterns'; import { regex_not_newline_characters } from '../../utils/patterns';
const regex_closing_script_tag = /<\/script\s*>/; const regex_closing_script_tag = /<\/script\s*>/;
const regex_starts_with_closing_script_tag = /^<\/script\s*>/;
function get_context(parser: Parser, attributes: any[], start: number): string { function get_context(parser: Parser, attributes: any[], start: number): string {
const context = attributes.find(attribute => attribute.name === 'context'); const context = attributes.find(attribute => attribute.name === 'context');
@ -32,7 +33,7 @@ export default function read_script(parser: Parser, start: number, attributes: N
} }
const source = parser.template.slice(0, script_start).replace(regex_not_newline_characters, ' ') + data; const source = parser.template.slice(0, script_start).replace(regex_not_newline_characters, ' ') + data;
parser.read(regex_closing_script_tag); parser.read(regex_starts_with_closing_script_tag);
let ast: Program; let ast: Program;

@ -7,6 +7,7 @@ import { Style } from '../../interfaces';
import parser_errors from '../errors'; import parser_errors from '../errors';
const regex_closing_style_tag = /<\/style\s*>/; const regex_closing_style_tag = /<\/style\s*>/;
const regex_starts_with_closing_style_tag = /^<\/style\s*>/;
export default function read_style(parser: Parser, start: number, attributes: Node[]): Style { export default function read_style(parser: Parser, start: number, attributes: Node[]): Style {
const content_start = parser.index; const content_start = parser.index;
@ -21,7 +22,7 @@ export default function read_style(parser: Parser, start: number, attributes: No
// discard styles when css is disabled // discard styles when css is disabled
if (parser.css_mode === 'none') { if (parser.css_mode === 'none') {
parser.read(regex_closing_style_tag); parser.read(regex_starts_with_closing_style_tag);
return null; return null;
} }
@ -76,7 +77,7 @@ export default function read_style(parser: Parser, start: number, attributes: No
} }
}); });
parser.read(regex_closing_style_tag); parser.read(regex_starts_with_closing_style_tag);
const end = parser.index; const end = parser.index;

@ -33,7 +33,7 @@ function trim_whitespace(block: TemplateNode, trim_before: boolean, trim_after:
} }
} }
const regex_whitespace_with_closing_curly_brace = /\s*}/; const regex_whitespace_with_closing_curly_brace = /^\s*}/;
export default function mustache(parser: Parser) { export default function mustache(parser: Parser) {
const start = parser.index; const start = parser.index;

@ -12,6 +12,9 @@ import { closing_tag_omitted, decode_character_references } from '../utils/html'
// eslint-disable-next-line no-useless-escape // eslint-disable-next-line no-useless-escape
const valid_tag_name = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/; const valid_tag_name = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/;
/** Invalid attribute characters if the attribute is not surrounded by quotes */
const regex_starts_with_invalid_attr_value = /^(\/>|[\s"'=<>`])/;
const meta_tags = new Map([ const meta_tags = new Map([
['svelte:head', 'Head'], ['svelte:head', 'Head'],
['svelte:options', 'Options'], ['svelte:options', 'Options'],
@ -293,7 +296,7 @@ function read_tag_name(parser: Parser) {
// eslint-disable-next-line no-useless-escape // eslint-disable-next-line no-useless-escape
const regex_token_ending_character = /[\s=\/>"']/; const regex_token_ending_character = /[\s=\/>"']/;
const regex_quote_characters = /["']/; const regex_starts_with_quote_characters = /^["']/;
function read_attribute(parser: Parser, unique_names: Set<string>) { function read_attribute(parser: Parser, unique_names: Set<string>) {
const start = parser.index; const start = parser.index;
@ -368,7 +371,7 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
parser.allow_whitespace(); parser.allow_whitespace();
value = read_attribute_value(parser); value = read_attribute_value(parser);
end = parser.index; end = parser.index;
} else if (parser.match_regex(regex_quote_characters)) { } else if (parser.match_regex(regex_starts_with_quote_characters)) {
parser.error(parser_errors.unexpected_token('='), parser.index); parser.error(parser_errors.unexpected_token('='), parser.index);
} }
@ -475,15 +478,13 @@ function read_attribute_value(parser: Parser) {
}]; }];
} }
const regex = (
quote_mark === "'" ? /'/ :
quote_mark === '"' ? /"/ :
/(\/>|[\s"'=<>`])/
);
let value; let value;
try { try {
value = read_sequence(parser, () => !!parser.match_regex(regex), 'in attribute value'); value = read_sequence(parser, () => {
// handle common case of quote marks existing outside of regex for performance reasons
if (quote_mark) return parser.match(quote_mark);
return !!parser.match_regex(regex_starts_with_invalid_attr_value);
}, 'in attribute value');
} catch (error) { } catch (error) {
if (error.code === 'parse-error') { if (error.code === 'parse-error') {
// if the attribute value didn't close + self-closing tag // if the attribute value didn't close + self-closing tag
@ -518,7 +519,7 @@ function read_sequence(parser: Parser, done: () => boolean, location: string): T
function flush(end: number) { function flush(end: number) {
if (current_chunk.raw) { if (current_chunk.raw) {
current_chunk.data = decode_character_references(current_chunk.raw); current_chunk.data = decode_character_references(current_chunk.raw, true);
current_chunk.end = end; current_chunk.end = end;
chunks.push(current_chunk); chunks.push(current_chunk);
} }

@ -19,7 +19,7 @@ export default function text(parser: Parser) {
end: parser.index, end: parser.index,
type: 'Text', type: 'Text',
raw: data, raw: data,
data: decode_character_references(data) data: decode_character_references(data, false)
}; };
parser.current().children.push(node); parser.current().children.push(node);

File diff suppressed because it is too large Load Diff

@ -35,12 +35,32 @@ const windows_1252 = [
376 376
]; ];
const entity_pattern = new RegExp( function reg_exp_entity(entity_name: string, is_attribute_value: boolean) {
`&(#?(?:x[\\w\\d]+|\\d+|${Object.keys(entities).join('|')}))(?:;|\\b)`, // https://html.spec.whatwg.org/multipage/parsing.html#named-character-reference-state
'g' // doesn't decode the html entity which not ends with ; and next character is =, number or alphabet in attribute value.
); if (is_attribute_value && !entity_name.endsWith(';')) {
return `${entity_name}\\b(?!=)`;
}
return entity_name;
}
function get_entity_pattern(is_attribute_value: boolean) {
const reg_exp_num = '#(?:x[a-fA-F\\d]+|\\d+)(?:;)?';
const reg_exp_entities = Object.keys(entities).map(entity_name => reg_exp_entity(entity_name, is_attribute_value));
const entity_pattern = new RegExp(
`&(${reg_exp_num}|${reg_exp_entities.join('|')})`,
'g'
);
return entity_pattern;
}
const entity_pattern_content = get_entity_pattern(false);
const entity_pattern_attr_value = get_entity_pattern(true);
export function decode_character_references(html: string) { export function decode_character_references(html: string, is_attribute_value: boolean) {
const entity_pattern = is_attribute_value ? entity_pattern_attr_value : entity_pattern_content;
return html.replace(entity_pattern, (match, entity) => { return html.replace(entity_pattern, (match, entity) => {
let code; let code;

@ -1,4 +1,4 @@
import { decode as decode_mappings } from 'sourcemap-codec'; import { decode as decode_mappings } from '@jridgewell/sourcemap-codec';
import { Processed } from './types'; import { Processed } from './types';
/** /**

@ -1,6 +1,7 @@
import { TemplateNode } from '../interfaces'; import { TemplateNode } from '../interfaces';
import { flatten } from './flatten'; import { flatten } from './flatten';
import { regex_whitespace } from './patterns'; import { regex_whitespace } from './patterns';
import { INode } from '../compile/nodes/interfaces';
const regex_svelte_ignore = /^\s*svelte-ignore\s+([\s\S]+)\s*$/m; const regex_svelte_ignore = /^\s*svelte-ignore\s+([\s\S]+)\s*$/m;
@ -33,3 +34,25 @@ export function extract_ignores_above_position(position: number, template_nodes:
return []; return [];
} }
export function extract_ignores_above_node(node: INode): string[] {
/**
* This utilizes the fact that node has a prev and a next attribute
* which means that it can find svelte-ignores along
* the nodes on the same level as itself who share the same parent.
*/
let cur_node = node.prev;
while (cur_node) {
if (cur_node.type !== 'Comment' && cur_node.type !== 'Text') {
return [];
}
if (cur_node.type === 'Comment' && cur_node.ignores.length) {
return cur_node.ignores;
}
cur_node = cur_node.prev;
}
return [];
}

@ -1,4 +1,4 @@
import { add_render_callback, flush, schedule_update, dirty_components } from './scheduler'; import { add_render_callback, flush, flush_render_callbacks, schedule_update, dirty_components } from './scheduler';
import { current_component, set_current_component } from './lifecycle'; import { current_component, set_current_component } from './lifecycle';
import { blank_object, is_empty, is_function, run, run_all, noop } from './utils'; import { blank_object, is_empty, is_function, run, run_all, noop } from './utils';
import { children, detach, start_hydrating, end_hydrating } from './dom'; import { children, detach, start_hydrating, end_hydrating } from './dom';
@ -51,6 +51,8 @@ export function mount_component(component, target, anchor, customElement) {
export function destroy_component(component, detaching) { export function destroy_component(component, detaching) {
const $$ = component.$$; const $$ = component.$$;
if ($$.fragment !== null) { if ($$.fragment !== null) {
flush_render_callbacks($$.after_update);
run_all($$.on_destroy); run_all($$.on_destroy);
$$.fragment && $$.fragment.d(detaching); $$.fragment && $$.fragment.d(detaching);

@ -49,10 +49,11 @@ export function detach_after_dev(before: Node) {
} }
} }
export function listen_dev(node: Node, event: string, handler: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions | EventListenerOptions, has_prevent_default?: boolean, has_stop_propagation?: boolean) { export function listen_dev(node: Node, event: string, handler: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions | EventListenerOptions, has_prevent_default?: boolean, has_stop_propagation?: boolean, has_stop_immediate_propagation?: boolean) {
const modifiers = options === true ? [ 'capture' ] : options ? Array.from(Object.keys(options)) : []; const modifiers = options === true ? [ 'capture' ] : options ? Array.from(Object.keys(options)) : [];
if (has_prevent_default) modifiers.push('preventDefault'); if (has_prevent_default) modifiers.push('preventDefault');
if (has_stop_propagation) modifiers.push('stopPropagation'); if (has_stop_propagation) modifiers.push('stopPropagation');
if (has_stop_immediate_propagation) modifiers.push('stopImmediatePropagation');
dispatch_dev('SvelteDOMAddEventListener', { node, event, handler, modifiers }); dispatch_dev('SvelteDOMAddEventListener', { node, event, handler, modifiers });

@ -13,7 +13,7 @@ export function end_hydrating() {
type NodeEx = Node & { type NodeEx = Node & {
claim_order?: number, claim_order?: number,
hydrate_init? : true, hydrate_init?: true,
actual_end_child?: NodeEx, actual_end_child?: NodeEx,
childNodes: NodeListOf<NodeEx>, childNodes: NodeListOf<NodeEx>,
}; };
@ -35,7 +35,7 @@ function init_hydrate(target: NodeEx) {
if (target.hydrate_init) return; if (target.hydrate_init) return;
target.hydrate_init = true; target.hydrate_init = true;
type NodeEx2 = NodeEx & {claim_order: number}; type NodeEx2 = NodeEx & { claim_order: number };
// We know that all children have claim_order values since the unclaimed have been detached if target is not <head> // We know that all children have claim_order values since the unclaimed have been detached if target is not <head>
let children: ArrayLike<NodeEx2> = target.childNodes as NodeListOf<NodeEx2>; let children: ArrayLike<NodeEx2> = target.childNodes as NodeListOf<NodeEx2>;
@ -260,7 +260,7 @@ export function listen(node: EventTarget, event: string, handler: EventListenerO
} }
export function prevent_default(fn) { export function prevent_default(fn) {
return function(event) { return function (event) {
event.preventDefault(); event.preventDefault();
// @ts-ignore // @ts-ignore
return fn.call(this, event); return fn.call(this, event);
@ -268,22 +268,30 @@ export function prevent_default(fn) {
} }
export function stop_propagation(fn) { export function stop_propagation(fn) {
return function(event) { return function (event) {
event.stopPropagation(); event.stopPropagation();
// @ts-ignore // @ts-ignore
return fn.call(this, event); return fn.call(this, event);
}; };
} }
export function stop_immediate_propagation(fn) {
return function (event) {
event.stopImmediatePropagation();
// @ts-ignore
return fn.call(this, event);
};
}
export function self(fn) { export function self(fn) {
return function(event) { return function (event) {
// @ts-ignore // @ts-ignore
if (event.target === this) fn.call(this, event); if (event.target === this) fn.call(this, event);
}; };
} }
export function trusted(fn) { export function trusted(fn) {
return function(event) { return function (event) {
// @ts-ignore // @ts-ignore
if (event.isTrusted) fn.call(this, event); if (event.isTrusted) fn.call(this, event);
}; };
@ -332,6 +340,10 @@ export function set_custom_element_data(node, prop, value) {
} }
} }
export function set_dynamic_element_data(tag: string) {
return (/-/.test(tag)) ? set_custom_element_data_map : set_attributes;
}
export function xlink_attr(node, attribute, value) { export function xlink_attr(node, attribute, value) {
node.setAttributeNS('http://www.w3.org/1999/xlink', attribute, value); node.setAttributeNS('http://www.w3.org/1999/xlink', attribute, value);
} }
@ -347,6 +359,53 @@ export function get_binding_group_value(group, __value, checked) {
return Array.from(value); return Array.from(value);
} }
export function init_binding_group(group) {
let _inputs: HTMLInputElement[];
return {
/* push */ p(...inputs: HTMLInputElement[]) {
_inputs = inputs;
_inputs.forEach(input => group.push(input));
},
/* remove */ r() {
_inputs.forEach(input => group.splice(group.indexOf(input), 1));
}
};
}
export function init_binding_group_dynamic(group, indexes: number[]) {
let _group: HTMLInputElement[] = get_binding_group(group);
let _inputs: HTMLInputElement[];
function get_binding_group(group) {
for (let i = 0; i < indexes.length; i++) {
group = group[indexes[i]] = group[indexes[i]] || [];
}
return group;
}
function push() {
_inputs.forEach(input => _group.push(input));
}
function remove() {
_inputs.forEach(input => _group.splice(_group.indexOf(input), 1));
}
return {
/* update */ u(new_indexes: number[]) {
indexes = new_indexes;
const new_group = get_binding_group(group);
if (new_group !== _group) {
remove();
_group = new_group;
push();
}
},
/* push */ p(...inputs: HTMLInputElement[]) {
_inputs = inputs;
push();
},
/* remove */ r: remove
};
}
export function to_number(value) { export function to_number(value) {
return value === '' ? null : +value; return value === '' ? null : +value;
} }
@ -380,7 +439,7 @@ export function children(element: Element) {
function init_claim_info(nodes: ChildNodeArray) { function init_claim_info(nodes: ChildNodeArray) {
if (nodes.claim_info === undefined) { if (nodes.claim_info === undefined) {
nodes.claim_info = {last_index: 0, total_claimed: 0}; nodes.claim_info = { last_index: 0, total_claimed: 0 };
} }
} }
@ -567,8 +626,16 @@ export function select_options(select, value) {
} }
} }
function first_enabled_option(select) {
for (const option of select.options) {
if (!option.disabled) {
return option;
}
}
}
export function select_value(select) { export function select_value(select) {
const selected_option = select.querySelector(':checked') || select.options[0]; const selected_option = select.querySelector(':checked') || first_enabled_option(select);
return selected_option && selected_option.__value; return selected_option && selected_option.__value;
} }
@ -624,6 +691,10 @@ export function add_resize_listener(node: HTMLElement, fn: () => void) {
iframe.src = 'about:blank'; iframe.src = 'about:blank';
iframe.onload = () => { iframe.onload = () => {
unsubscribe = listen(iframe.contentWindow, 'resize', fn); unsubscribe = listen(iframe.contentWindow, 'resize', fn);
// make sure an initial resize event is fired _after_ the iframe is loaded (which is asynchronous)
// see https://github.com/sveltejs/svelte/issues/4233
fn();
}; };
} }
@ -644,7 +715,7 @@ export function toggle_class(element, name, toggle) {
element.classList[toggle ? 'add' : 'remove'](name); element.classList[toggle ? 'add' : 'remove'](name);
} }
export function custom_event<T=any>(type: string, detail?: T, { bubbles = false, cancelable = false } = {}): CustomEvent<T> { export function custom_event<T = any>(type: string, detail?: T, { bubbles = false, cancelable = false } = {}): CustomEvent<T> {
const e: CustomEvent<T> = document.createEvent('CustomEvent'); const e: CustomEvent<T> = document.createEvent('CustomEvent');
e.initCustomEvent(type, bubbles, cancelable, detail); e.initCustomEvent(type, bubbles, cancelable, detail);
return e; return e;
@ -682,7 +753,7 @@ export class HtmlTag {
// html tag nodes // html tag nodes
n: ChildNode[]; n: ChildNode[];
// target // target
t: HTMLElement | SVGElement; t: HTMLElement | SVGElement | DocumentFragment;
// anchor // anchor
a: HTMLElement | SVGElement; a: HTMLElement | SVGElement;
@ -702,8 +773,9 @@ export class HtmlTag {
) { ) {
if (!this.e) { if (!this.e) {
if (this.is_svg) this.e = svg_element(target.nodeName as keyof SVGElementTagNameMap); if (this.is_svg) this.e = svg_element(target.nodeName as keyof SVGElementTagNameMap);
else this.e = element(target.nodeName as keyof HTMLElementTagNameMap); /** #7364 target for <template> may be provided as #document-fragment(11) */
this.t = target; else this.e = element((target.nodeType === 11 ? 'TEMPLATE' : target.nodeName) as keyof HTMLElementTagNameMap);
this.t = target.tagName !== 'TEMPLATE' ? target : (target as HTMLTemplateElement).content;
this.c(html); this.c(html);
} }
@ -712,7 +784,7 @@ export class HtmlTag {
h(html: string) { h(html: string) {
this.e.innerHTML = html; this.e.innerHTML = html;
this.n = Array.from(this.e.childNodes); this.n = Array.from(this.e.nodeName === 'TEMPLATE' ? (this.e as HTMLTemplateElement).content.childNodes : this.e.childNodes);
} }
i(anchor) { i(anchor) {

@ -1,4 +1,5 @@
import { transition_in, transition_out } from './transitions'; import { transition_in, transition_out } from './transitions';
import { run_all } from './utils';
export function destroy_block(block, lookup) { export function destroy_block(block, lookup) {
block.d(1); block.d(1);
@ -32,6 +33,7 @@ export function update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list
const new_blocks = []; const new_blocks = [];
const new_lookup = new Map(); const new_lookup = new Map();
const deltas = new Map(); const deltas = new Map();
const updates = [];
i = n; i = n;
while (i--) { while (i--) {
@ -43,7 +45,8 @@ export function update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list
block = create_each_block(key, child_ctx); block = create_each_block(key, child_ctx);
block.c(); block.c();
} else if (dynamic) { } else if (dynamic) {
block.p(child_ctx, dirty); // defer updates until all the DOM shuffling is done
updates.push(() => block.p(child_ctx, dirty));
} }
new_lookup.set(key, new_blocks[i] = block); new_lookup.set(key, new_blocks[i] = block);
@ -99,6 +102,8 @@ export function update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list
while (n) insert(new_blocks[n - 1]); while (n) insert(new_blocks[n - 1]);
run_all(updates);
return new_blocks; return new_blocks;
} }

@ -5,7 +5,7 @@ export const dirty_components = [];
export const intros = { enabled: false }; export const intros = { enabled: false };
export const binding_callbacks = []; export const binding_callbacks = [];
const render_callbacks = []; let render_callbacks = [];
const flush_callbacks = []; const flush_callbacks = [];
const resolved_promise = Promise.resolve(); const resolved_promise = Promise.resolve();
@ -122,3 +122,14 @@ function update($$) {
$$.after_update.forEach(add_render_callback); $$.after_update.forEach(add_render_callback);
} }
} }
/**
* Useful for example to execute remaining `afterUpdate` callbacks before executing `destroy`.
*/
export function flush_render_callbacks(fns: Function[]): void {
const filtered = [];
const targets = [];
render_callbacks.forEach((c) => fns.indexOf(c) === -1 ? filtered.push(c) : targets.push(c));
targets.forEach((c) => c());
render_callbacks = filtered;
}

@ -98,7 +98,7 @@ export function writable<T>(value?: T, start: StartStopNotifier<T> = noop): Writ
return () => { return () => {
subscribers.delete(subscriber); subscribers.delete(subscriber);
if (subscribers.size === 0) { if (subscribers.size === 0 && stop) {
stop(); stop();
stop = null; stop = null;
} }
@ -207,6 +207,17 @@ export function derived<T>(stores: Stores, fn: Function, initial_value?: T): Rea
}); });
} }
/**
* Takes a store and returns a new one derived from the old one that is readable.
*
* @param store - store to make readonly
*/
export function readonly<T>(store: Readable<T>): Readable<T> {
return {
subscribe: store.subscribe.bind(store)
};
}
/** /**
* Get the current value from a store by subscribing and immediately unsubscribing. * Get the current value from a store by subscribing and immediately unsubscribing.
* @param store readable * @param store readable

@ -98,23 +98,27 @@ export interface SlideParams {
delay?: number; delay?: number;
duration?: number; duration?: number;
easing?: EasingFunction; easing?: EasingFunction;
axis?: 'x' | 'y';
} }
export function slide(node: Element, { export function slide(node: Element, {
delay = 0, delay = 0,
duration = 400, duration = 400,
easing = cubicOut easing = cubicOut,
axis = 'y'
}: SlideParams = {}): TransitionConfig { }: SlideParams = {}): TransitionConfig {
const style = getComputedStyle(node); const style = getComputedStyle(node);
const opacity = +style.opacity; const opacity = +style.opacity;
const height = parseFloat(style.height); const primary_property = axis === 'y' ? 'height' : 'width';
const padding_top = parseFloat(style.paddingTop); const primary_property_value = parseFloat(style[primary_property]);
const padding_bottom = parseFloat(style.paddingBottom); const secondary_properties = axis === 'y' ? ['top', 'bottom'] : ['left', 'right'];
const margin_top = parseFloat(style.marginTop); const capitalized_secondary_properties = secondary_properties.map((e) => `${e[0].toUpperCase()}${e.slice(1)}`);
const margin_bottom = parseFloat(style.marginBottom); const padding_start_value = parseFloat(style[`padding${capitalized_secondary_properties[0]}`]);
const border_top_width = parseFloat(style.borderTopWidth); const padding_end_value = parseFloat(style[`padding${capitalized_secondary_properties[1]}`]);
const border_bottom_width = parseFloat(style.borderBottomWidth); const margin_start_value = parseFloat(style[`margin${capitalized_secondary_properties[0]}`]);
const margin_end_value = parseFloat(style[`margin${capitalized_secondary_properties[1]}`]);
const border_width_start_value = parseFloat(style[`border${capitalized_secondary_properties[0]}Width`]);
const border_width_end_value = parseFloat(style[`border${capitalized_secondary_properties[1]}Width`]);
return { return {
delay, delay,
duration, duration,
@ -122,13 +126,13 @@ export function slide(node: Element, {
css: t => css: t =>
'overflow: hidden;' + 'overflow: hidden;' +
`opacity: ${Math.min(t * 20, 1) * opacity};` + `opacity: ${Math.min(t * 20, 1) * opacity};` +
`height: ${t * height}px;` + `${primary_property}: ${t * primary_property_value}px;` +
`padding-top: ${t * padding_top}px;` + `padding-${secondary_properties[0]}: ${t * padding_start_value}px;` +
`padding-bottom: ${t * padding_bottom}px;` + `padding-${secondary_properties[1]}: ${t * padding_end_value}px;` +
`margin-top: ${t * margin_top}px;` + `margin-${secondary_properties[0]}: ${t * margin_start_value}px;` +
`margin-bottom: ${t * margin_bottom}px;` + `margin-${secondary_properties[1]}: ${t * margin_end_value}px;` +
`border-top-width: ${t * border_top_width}px;` + `border-${secondary_properties[0]}-width: ${t * border_width_start_value}px;` +
`border-bottom-width: ${t * border_bottom_width}px;` `border-${secondary_properties[1]}-width: ${t * border_width_end_value}px;`
}; };
} }
@ -211,7 +215,7 @@ export interface CrossfadeParams {
easing?: EasingFunction; easing?: EasingFunction;
} }
type ClientRectMap = Map<any, { rect: ClientRect }>; type ClientRectMap = Map<any, Element>;
export function crossfade({ fallback, ...defaults }: CrossfadeParams & { export function crossfade({ fallback, ...defaults }: CrossfadeParams & {
fallback?: (node: Element, params: CrossfadeParams, intro: boolean) => TransitionConfig; fallback?: (node: Element, params: CrossfadeParams, intro: boolean) => TransitionConfig;
@ -232,13 +236,14 @@ export function crossfade({ fallback, ...defaults }: CrossfadeParams & {
const to_receive: ClientRectMap = new Map(); const to_receive: ClientRectMap = new Map();
const to_send: ClientRectMap = new Map(); const to_send: ClientRectMap = new Map();
function crossfade(from: ClientRect, node: Element, params: CrossfadeParams): TransitionConfig { function crossfade(from_node: Element, node: Element, params: CrossfadeParams): TransitionConfig {
const { const {
delay = 0, delay = 0,
duration = d => Math.sqrt(d) * 30, duration = d => Math.sqrt(d) * 30,
easing = cubicOut easing = cubicOut
} = assign(assign({}, defaults), params); } = assign(assign({}, defaults), params);
const from = from_node.getBoundingClientRect();
const to = node.getBoundingClientRect(); const to = node.getBoundingClientRect();
const dx = from.left - to.left; const dx = from.left - to.left;
const dy = from.top - to.top; const dy = from.top - to.top;
@ -264,16 +269,14 @@ export function crossfade({ fallback, ...defaults }: CrossfadeParams & {
function transition(items: ClientRectMap, counterparts: ClientRectMap, intro: boolean) { function transition(items: ClientRectMap, counterparts: ClientRectMap, intro: boolean) {
return (node: Element, params: CrossfadeParams & { key: any }) => { return (node: Element, params: CrossfadeParams & { key: any }) => {
items.set(params.key, { items.set(params.key, node);
rect: node.getBoundingClientRect()
});
return () => { return () => {
if (counterparts.has(params.key)) { if (counterparts.has(params.key)) {
const { rect } = counterparts.get(params.key); const other_node = counterparts.get(params.key);
counterparts.delete(params.key); counterparts.delete(params.key);
return crossfade(rect, node, params); return crossfade(other_node, node, params);
} }
// if the node is disappearing altogether // if the node is disappearing altogether

@ -1,5 +1,4 @@
// source: https://html.spec.whatwg.org/multipage/indices.html const _boolean_attributes = [
export const boolean_attributes = new Set([
'allowfullscreen', 'allowfullscreen',
'allowpaymentrequest', 'allowpaymentrequest',
'async', 'async',
@ -26,4 +25,12 @@ export const boolean_attributes = new Set([
'required', 'required',
'reversed', 'reversed',
'selected' 'selected'
]); ] as const;
export type BooleanAttributes = typeof _boolean_attributes[number];
/**
* List of HTML boolean attributes (e.g. `<input disabled>`).
* Source: https://html.spec.whatwg.org/multipage/indices.html
*/
export const boolean_attributes: Set<string> = new Set([..._boolean_attributes]);

@ -0,0 +1,25 @@
export default {
warnings: [{
filename: 'SvelteComponent.svelte',
code: 'css-unused-selector',
message: 'Unused CSS selector "img[alt=""]"',
start: {
character: 87,
column: 1,
line: 8
},
end: {
character: 98,
column: 12,
line: 8
},
pos: 87,
frame: `
6: }
7:
8: img[alt=""] {
^
9: border: 1px solid red;
10: }`
}]
};

@ -0,0 +1 @@
img[alt].svelte-xyz{border:1px solid green}

@ -0,0 +1 @@
<img alt="a foo" class="svelte-xyz" src="foo.jpg">

@ -0,0 +1,11 @@
<img src="foo.jpg" alt="a foo" />
<style>
img[alt] {
border: 1px solid green;
}
img[alt=""] {
border: 1px solid red;
}
</style>

@ -0,0 +1,6 @@
export default {
options: {
generate: 'ssr',
dev: true
}
};

@ -0,0 +1,25 @@
/* generated by Svelte vX.Y.Z */
import { add_attribute, create_ssr_component, escape } from "svelte/internal";
const const1 = 1;
const const2 = 'const2';
function foo() {
return '';
}
const Component = create_ssr_component(($$result, $$props, $$bindings, slots) => {
return `
<div class="class1 class2" style="color:red;">-</div>
<div${add_attribute("class", const1, 0)}>-</div>
<div${add_attribute("class", const1, 0)}>-</div>
<div class="${"class1 " + escape('class2', true)}">-</div>
<div class="${"class1 " + escape(const2, true)}">-</div>
<div class="${"class1 " + escape(const2, true)}"${add_attribute("style", foo(), 0)}>-</div>`;
});
export default Component;

@ -0,0 +1,20 @@
<script>
const const1 = 1;
const const2 = 'const2';
function foo() {
return '';
}
</script>
<!-- canonical case - only markup -->
<div class="class1 class2" style="color:red;">-</div>
<!-- various forms of variable syntax -->
<div class="{const1}">-</div>
<div class={const1}>-</div>
<!-- mixed static string + expressions -->
<div class="class1 {'class2'}">-</div>
<div class="class1 {const2}">-</div>
<div class="class1 {const2}" style={foo()}>-</div>

@ -114,7 +114,9 @@ function create_fragment(ctx) {
}, },
m: function mount(target, anchor) { m: function mount(target, anchor) {
for (let i = 0; i < each_blocks.length; i += 1) { for (let i = 0; i < each_blocks.length; i += 1) {
each_blocks[i].m(target, anchor); if (each_blocks[i]) {
each_blocks[i].m(target, anchor);
}
} }
insert_dev(target, t0, anchor); insert_dev(target, t0, anchor);

@ -108,7 +108,9 @@ function create_fragment(ctx) {
}, },
m: function mount(target, anchor) { m: function mount(target, anchor) {
for (let i = 0; i < each_blocks.length; i += 1) { for (let i = 0; i < each_blocks.length; i += 1) {
each_blocks[i].m(target, anchor); if (each_blocks[i]) {
each_blocks[i].m(target, anchor);
}
} }
insert_dev(target, t0, anchor); insert_dev(target, t0, anchor);

@ -86,7 +86,9 @@ function create_fragment(ctx) {
}, },
m: function mount(target, anchor) { m: function mount(target, anchor) {
for (let i = 0; i < each_blocks.length; i += 1) { for (let i = 0; i < each_blocks.length; i += 1) {
each_blocks[i].m(target, anchor); if (each_blocks[i]) {
each_blocks[i].m(target, anchor);
}
} }
insert_dev(target, each_1_anchor, anchor); insert_dev(target, each_1_anchor, anchor);

@ -63,7 +63,9 @@ function create_fragment(ctx) {
}, },
m(target, anchor) { m(target, anchor) {
for (let i = 0; i < each_blocks.length; i += 1) { for (let i = 0; i < each_blocks.length; i += 1) {
each_blocks[i].m(target, anchor); if (each_blocks[i]) {
each_blocks[i].m(target, anchor);
}
} }
insert(target, each_1_anchor, anchor); insert(target, each_1_anchor, anchor);

@ -63,7 +63,9 @@ function create_fragment(ctx) {
}, },
m(target, anchor) { m(target, anchor) {
for (let i = 0; i < 5; i += 1) { for (let i = 0; i < 5; i += 1) {
each_blocks[i].m(target, anchor); if (each_blocks[i]) {
each_blocks[i].m(target, anchor);
}
} }
insert(target, each_1_anchor, anchor); insert(target, each_1_anchor, anchor);

@ -104,7 +104,9 @@ function create_fragment(ctx) {
}, },
m(target, anchor) { m(target, anchor) {
for (let i = 0; i < each_blocks.length; i += 1) { for (let i = 0; i < each_blocks.length; i += 1) {
each_blocks[i].m(target, anchor); if (each_blocks[i]) {
each_blocks[i].m(target, anchor);
}
} }
insert(target, t0, anchor); insert(target, t0, anchor);

@ -87,7 +87,9 @@ function create_fragment(ctx) {
}, },
m(target, anchor) { m(target, anchor) {
for (let i = 0; i < each_blocks.length; i += 1) { for (let i = 0; i < each_blocks.length; i += 1) {
each_blocks[i].m(target, anchor); if (each_blocks[i]) {
each_blocks[i].m(target, anchor);
}
} }
insert(target, each_1_anchor, anchor); insert(target, each_1_anchor, anchor);

@ -72,7 +72,9 @@ function create_fragment(ctx) {
}, },
m(target, anchor) { m(target, anchor) {
for (let i = 0; i < each_blocks.length; i += 1) { for (let i = 0; i < each_blocks.length; i += 1) {
each_blocks[i].m(target, anchor); if (each_blocks[i]) {
each_blocks[i].m(target, anchor);
}
} }
insert(target, each_1_anchor, anchor); insert(target, each_1_anchor, anchor);

@ -12,6 +12,7 @@ import {
run_all, run_all,
safe_not_equal, safe_not_equal,
space, space,
stop_immediate_propagation,
stop_propagation stop_propagation
} from "svelte/internal"; } from "svelte/internal";
@ -24,6 +25,8 @@ function create_fragment(ctx) {
let button1; let button1;
let t5; let t5;
let button2; let button2;
let t7;
let button3;
let mounted; let mounted;
let dispose; let dispose;
@ -41,6 +44,9 @@ function create_fragment(ctx) {
t5 = space(); t5 = space();
button2 = element("button"); button2 = element("button");
button2.textContent = "or me!"; button2.textContent = "or me!";
t7 = space();
button3 = element("button");
button3.textContent = "or me!";
}, },
m(target, anchor) { m(target, anchor) {
insert(target, div1, anchor); insert(target, div1, anchor);
@ -51,6 +57,8 @@ function create_fragment(ctx) {
append(div1, button1); append(div1, button1);
append(div1, t5); append(div1, t5);
append(div1, button2); append(div1, button2);
append(div1, t7);
append(div1, button3);
if (!mounted) { if (!mounted) {
dispose = [ dispose = [
@ -58,6 +66,8 @@ function create_fragment(ctx) {
listen(button0, "click", stop_propagation(prevent_default(handleClick))), listen(button0, "click", stop_propagation(prevent_default(handleClick))),
listen(button1, "click", handleClick, { once: true, capture: true }), listen(button1, "click", handleClick, { once: true, capture: true }),
listen(button2, "click", handleClick, true), listen(button2, "click", handleClick, true),
listen(button3, "click", stop_immediate_propagation(handleClick)),
listen(button3, "click", handleTouchstart),
listen(div1, "touchstart", handleTouchstart, { passive: true }) listen(div1, "touchstart", handleTouchstart, { passive: true })
]; ];

@ -13,4 +13,9 @@
<button on:click|stopPropagation|preventDefault={handleClick}>click me</button> <button on:click|stopPropagation|preventDefault={handleClick}>click me</button>
<button on:click|once|capture={handleClick}>or me</button> <button on:click|once|capture={handleClick}>or me</button>
<button on:click|capture={handleClick}>or me!</button> <button on:click|capture={handleClick}>or me!</button>
<button
on:click|stopImmediatePropagation={handleClick}
on:click={handleTouchstart}>
or me!
</button>
</div> </div>

@ -30,18 +30,19 @@ function create_fragment(ctx) {
audio_updating = true; audio_updating = true;
} }
/*audio_timeupdate_handler*/ ctx[13].call(audio); /*audio_timeupdate_handler*/ ctx[14].call(audio);
} }
return { return {
c() { c() {
audio = element("audio"); audio = element("audio");
if (/*buffered*/ ctx[0] === void 0) add_render_callback(() => /*audio_progress_handler*/ ctx[11].call(audio)); if (/*buffered*/ ctx[0] === void 0) add_render_callback(() => /*audio_progress_handler*/ ctx[12].call(audio));
if (/*buffered*/ ctx[0] === void 0 || /*seekable*/ ctx[1] === void 0) add_render_callback(() => /*audio_loadedmetadata_handler*/ ctx[12].call(audio)); if (/*buffered*/ ctx[0] === void 0 || /*seekable*/ ctx[1] === void 0) add_render_callback(() => /*audio_loadedmetadata_handler*/ ctx[13].call(audio));
if (/*played*/ ctx[2] === void 0 || /*currentTime*/ ctx[3] === void 0 || /*ended*/ ctx[10] === void 0) add_render_callback(audio_timeupdate_handler); if (/*played*/ ctx[2] === void 0 || /*currentTime*/ ctx[3] === void 0 || /*ended*/ ctx[10] === void 0) add_render_callback(audio_timeupdate_handler);
if (/*duration*/ ctx[4] === void 0) add_render_callback(() => /*audio_durationchange_handler*/ ctx[14].call(audio)); if (/*duration*/ ctx[4] === void 0) add_render_callback(() => /*audio_durationchange_handler*/ ctx[15].call(audio));
if (/*seeking*/ ctx[9] === void 0) add_render_callback(() => /*audio_seeking_seeked_handler*/ ctx[18].call(audio)); if (/*seeking*/ ctx[9] === void 0) add_render_callback(() => /*audio_seeking_seeked_handler*/ ctx[19].call(audio));
if (/*ended*/ ctx[10] === void 0) add_render_callback(() => /*audio_ended_handler*/ ctx[19].call(audio)); if (/*ended*/ ctx[10] === void 0) add_render_callback(() => /*audio_ended_handler*/ ctx[20].call(audio));
if (/*readyState*/ ctx[11] === void 0) add_render_callback(() => /*audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[21].call(audio));
}, },
m(target, anchor) { m(target, anchor) {
insert(target, audio, anchor); insert(target, audio, anchor);
@ -58,17 +59,24 @@ function create_fragment(ctx) {
if (!mounted) { if (!mounted) {
dispose = [ dispose = [
listen(audio, "progress", /*audio_progress_handler*/ ctx[11]), listen(audio, "progress", /*audio_progress_handler*/ ctx[12]),
listen(audio, "loadedmetadata", /*audio_loadedmetadata_handler*/ ctx[12]), listen(audio, "loadedmetadata", /*audio_loadedmetadata_handler*/ ctx[13]),
listen(audio, "timeupdate", audio_timeupdate_handler), listen(audio, "timeupdate", audio_timeupdate_handler),
listen(audio, "durationchange", /*audio_durationchange_handler*/ ctx[14]), listen(audio, "durationchange", /*audio_durationchange_handler*/ ctx[15]),
listen(audio, "play", /*audio_play_pause_handler*/ ctx[15]), listen(audio, "play", /*audio_play_pause_handler*/ ctx[16]),
listen(audio, "pause", /*audio_play_pause_handler*/ ctx[15]), listen(audio, "pause", /*audio_play_pause_handler*/ ctx[16]),
listen(audio, "volumechange", /*audio_volumechange_handler*/ ctx[16]), listen(audio, "volumechange", /*audio_volumechange_handler*/ ctx[17]),
listen(audio, "ratechange", /*audio_ratechange_handler*/ ctx[17]), listen(audio, "ratechange", /*audio_ratechange_handler*/ ctx[18]),
listen(audio, "seeking", /*audio_seeking_seeked_handler*/ ctx[18]), listen(audio, "seeking", /*audio_seeking_seeked_handler*/ ctx[19]),
listen(audio, "seeked", /*audio_seeking_seeked_handler*/ ctx[18]), listen(audio, "seeked", /*audio_seeking_seeked_handler*/ ctx[19]),
listen(audio, "ended", /*audio_ended_handler*/ ctx[19]) listen(audio, "ended", /*audio_ended_handler*/ ctx[20]),
listen(audio, "loadedmetadata", /*audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[21]),
listen(audio, "loadeddata", /*audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[21]),
listen(audio, "canplay", /*audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[21]),
listen(audio, "canplaythrough", /*audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[21]),
listen(audio, "playing", /*audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[21]),
listen(audio, "waiting", /*audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[21]),
listen(audio, "emptied", /*audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[21])
]; ];
mounted = true; mounted = true;
@ -119,6 +127,7 @@ function instance($$self, $$props, $$invalidate) {
let { playbackRate } = $$props; let { playbackRate } = $$props;
let { seeking } = $$props; let { seeking } = $$props;
let { ended } = $$props; let { ended } = $$props;
let { readyState } = $$props;
function audio_progress_handler() { function audio_progress_handler() {
buffered = time_ranges_to_array(this.buffered); buffered = time_ranges_to_array(this.buffered);
@ -173,6 +182,11 @@ function instance($$self, $$props, $$invalidate) {
$$invalidate(10, ended); $$invalidate(10, ended);
} }
function audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler() {
readyState = this.readyState;
$$invalidate(11, readyState);
}
$$self.$$set = $$props => { $$self.$$set = $$props => {
if ('buffered' in $$props) $$invalidate(0, buffered = $$props.buffered); if ('buffered' in $$props) $$invalidate(0, buffered = $$props.buffered);
if ('seekable' in $$props) $$invalidate(1, seekable = $$props.seekable); if ('seekable' in $$props) $$invalidate(1, seekable = $$props.seekable);
@ -185,6 +199,7 @@ function instance($$self, $$props, $$invalidate) {
if ('playbackRate' in $$props) $$invalidate(8, playbackRate = $$props.playbackRate); if ('playbackRate' in $$props) $$invalidate(8, playbackRate = $$props.playbackRate);
if ('seeking' in $$props) $$invalidate(9, seeking = $$props.seeking); if ('seeking' in $$props) $$invalidate(9, seeking = $$props.seeking);
if ('ended' in $$props) $$invalidate(10, ended = $$props.ended); if ('ended' in $$props) $$invalidate(10, ended = $$props.ended);
if ('readyState' in $$props) $$invalidate(11, readyState = $$props.readyState);
}; };
return [ return [
@ -199,6 +214,7 @@ function instance($$self, $$props, $$invalidate) {
playbackRate, playbackRate,
seeking, seeking,
ended, ended,
readyState,
audio_progress_handler, audio_progress_handler,
audio_loadedmetadata_handler, audio_loadedmetadata_handler,
audio_timeupdate_handler, audio_timeupdate_handler,
@ -207,7 +223,8 @@ function instance($$self, $$props, $$invalidate) {
audio_volumechange_handler, audio_volumechange_handler,
audio_ratechange_handler, audio_ratechange_handler,
audio_seeking_seeked_handler, audio_seeking_seeked_handler,
audio_ended_handler audio_ended_handler,
audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler
]; ];
} }
@ -226,7 +243,8 @@ class Component extends SvelteComponent {
muted: 7, muted: 7,
playbackRate: 8, playbackRate: 8,
seeking: 9, seeking: 9,
ended: 10 ended: 10,
readyState: 11
}); });
} }
} }

@ -10,6 +10,7 @@
export let playbackRate; export let playbackRate;
export let seeking; export let seeking;
export let ended; export let ended;
export let readyState;
</script> </script>
<audio bind:buffered bind:seekable bind:played bind:currentTime bind:duration bind:paused bind:volume bind:muted bind:playbackRate bind:seeking bind:ended/> <audio bind:buffered bind:seekable bind:played bind:currentTime bind:duration bind:paused bind:volume bind:muted bind:playbackRate bind:seeking bind:ended bind:readyState/>

@ -0,0 +1,95 @@
/* generated by Svelte vX.Y.Z */
import {
SvelteComponent,
add_render_callback,
attr,
detach,
element,
init,
insert,
listen,
noop,
safe_not_equal,
set_data,
space,
src_url_equal,
text
} from "svelte/internal";
function create_fragment(ctx) {
let img;
let img_src_value;
let t0;
let t1;
let t2;
let t3;
let mounted;
let dispose;
return {
c() {
img = element("img");
t0 = space();
t1 = text(/*naturalWidth*/ ctx[0]);
t2 = text(" x ");
t3 = text(/*naturalHeight*/ ctx[1]);
if (!src_url_equal(img.src, img_src_value = "something.jpg")) attr(img, "src", img_src_value);
if (/*naturalWidth*/ ctx[0] === void 0 || /*naturalHeight*/ ctx[1] === void 0) add_render_callback(() => /*img_load_handler*/ ctx[2].call(img));
},
m(target, anchor) {
insert(target, img, anchor);
insert(target, t0, anchor);
insert(target, t1, anchor);
insert(target, t2, anchor);
insert(target, t3, anchor);
if (!mounted) {
dispose = listen(img, "load", /*img_load_handler*/ ctx[2]);
mounted = true;
}
},
p(ctx, [dirty]) {
if (dirty & /*naturalWidth*/ 1) set_data(t1, /*naturalWidth*/ ctx[0]);
if (dirty & /*naturalHeight*/ 2) set_data(t3, /*naturalHeight*/ ctx[1]);
},
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(img);
if (detaching) detach(t0);
if (detaching) detach(t1);
if (detaching) detach(t2);
if (detaching) detach(t3);
mounted = false;
dispose();
}
};
}
function instance($$self, $$props, $$invalidate) {
let { naturalWidth = 0 } = $$props;
let { naturalHeight = 0 } = $$props;
function img_load_handler() {
naturalWidth = this.naturalWidth;
naturalHeight = this.naturalHeight;
$$invalidate(0, naturalWidth);
$$invalidate(1, naturalHeight);
}
$$self.$$set = $$props => {
if ('naturalWidth' in $$props) $$invalidate(0, naturalWidth = $$props.naturalWidth);
if ('naturalHeight' in $$props) $$invalidate(1, naturalHeight = $$props.naturalHeight);
};
return [naturalWidth, naturalHeight, img_load_handler];
}
class Component extends SvelteComponent {
constructor(options) {
super();
init(this, options, instance, create_fragment, safe_not_equal, { naturalWidth: 0, naturalHeight: 1 });
}
}
export default Component;

@ -0,0 +1,8 @@
<script>
export let naturalWidth = 0;
export let naturalHeight = 0;
</script>
<img src="something.jpg" bind:naturalWidth bind:naturalHeight>
{naturalWidth} x {naturalHeight}

@ -9,7 +9,6 @@ import {
noop, noop,
safe_not_equal, safe_not_equal,
space, space,
subscribe,
toggle_class toggle_class
} from "svelte/internal"; } from "svelte/internal";
@ -133,13 +132,8 @@ let reactiveModuleVar = Math.random();
function instance($$self, $$props, $$invalidate) { function instance($$self, $$props, $$invalidate) {
let reactiveDeclaration; let reactiveDeclaration;
let $reactiveStoreVal; let $reactiveStoreVal;
let $reactiveDeclaration;
let $reactiveDeclaration,
$$unsubscribe_reactiveDeclaration = noop,
$$subscribe_reactiveDeclaration = () => ($$unsubscribe_reactiveDeclaration(), $$unsubscribe_reactiveDeclaration = subscribe(reactiveDeclaration, $$value => $$invalidate(3, $reactiveDeclaration = $$value)), reactiveDeclaration);
component_subscribe($$self, reactiveStoreVal, $$value => $$invalidate(2, $reactiveStoreVal = $$value)); component_subscribe($$self, reactiveStoreVal, $$value => $$invalidate(2, $reactiveStoreVal = $$value));
$$self.$$.on_destroy.push(() => $$unsubscribe_reactiveDeclaration());
nonReactiveGlobal = Math.random(); nonReactiveGlobal = Math.random();
const reactiveConst = { x: Math.random() }; const reactiveConst = { x: Math.random() };
reactiveModuleVar += 1; reactiveModuleVar += 1;
@ -148,7 +142,8 @@ function instance($$self, $$props, $$invalidate) {
reactiveConst.x += 1; reactiveConst.x += 1;
} }
$: $$subscribe_reactiveDeclaration($$invalidate(1, reactiveDeclaration = reactiveModuleVar * 2)); $: reactiveDeclaration = reactiveModuleVar * 2;
component_subscribe($$self, reactiveDeclaration, $$value => $$invalidate(3, $reactiveDeclaration = $$value));
return [reactiveConst, reactiveDeclaration, $reactiveStoreVal, $reactiveDeclaration]; return [reactiveConst, reactiveDeclaration, $reactiveStoreVal, $reactiveDeclaration];
} }

@ -0,0 +1,60 @@
/* generated by Svelte vX.Y.Z */
import {
SvelteComponent,
detach,
element,
init,
insert,
noop,
safe_not_equal,
set_data,
space,
text
} from "svelte/internal";
function create_fragment(ctx) {
let h1;
let t3;
let t4;
return {
c() {
h1 = element("h1");
h1.textContent = `Hello ${name}!`;
t3 = space();
t4 = text(/*foo*/ ctx[0]);
},
m(target, anchor) {
insert(target, h1, anchor);
insert(target, t3, anchor);
insert(target, t4, anchor);
},
p(ctx, [dirty]) {
if (dirty & /*foo*/ 1) set_data(t4, /*foo*/ ctx[0]);
},
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(h1);
if (detaching) detach(t3);
if (detaching) detach(t4);
}
};
}
let name = 'world';
function instance($$self) {
let foo;
$: foo = name + name;
return [foo];
}
class Component extends SvelteComponent {
constructor(options) {
super();
init(this, options, instance, create_fragment, safe_not_equal, {});
}
}
export default Component;

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

@ -2,20 +2,16 @@
import { import {
SvelteComponent, SvelteComponent,
append, append,
assign,
bubble, bubble,
detach, detach,
element, element,
empty,
get_spread_update,
init, init,
insert, insert,
listen, listen,
noop, noop,
run_all, run_all,
safe_not_equal, safe_not_equal,
set_attributes, set_dynamic_element_data
set_custom_element_data_map
} from "svelte/internal"; } from "svelte/internal";
function create_dynamic_element(ctx) { function create_dynamic_element(ctx) {
@ -23,36 +19,13 @@ function create_dynamic_element(ctx) {
let svelte_element0; let svelte_element0;
let mounted; let mounted;
let dispose; let dispose;
let svelte_element0_levels = [{ class: "inner" }];
let svelte_element0_data = {};
for (let i = 0; i < svelte_element0_levels.length; i += 1) {
svelte_element0_data = assign(svelte_element0_data, svelte_element0_levels[i]);
}
let svelte_element1_levels = [{ class: "outer" }];
let svelte_element1_data = {};
for (let i = 0; i < svelte_element1_levels.length; i += 1) {
svelte_element1_data = assign(svelte_element1_data, svelte_element1_levels[i]);
}
return { return {
c() { c() {
svelte_element1 = element(a); svelte_element1 = element(a);
svelte_element0 = element(span); svelte_element0 = element(span);
set_dynamic_element_data(span)(svelte_element0, { class: "inner" });
if ((/-/).test(span)) { set_dynamic_element_data(a)(svelte_element1, { class: "outer" });
set_custom_element_data_map(svelte_element0, svelte_element0_data);
} else {
set_attributes(svelte_element0, svelte_element0_data);
}
if ((/-/).test(a)) {
set_custom_element_data_map(svelte_element1, svelte_element1_data);
} else {
set_attributes(svelte_element1, svelte_element1_data);
}
}, },
m(target, anchor) { m(target, anchor) {
insert(target, svelte_element1, anchor); insert(target, svelte_element1, anchor);
@ -69,23 +42,7 @@ function create_dynamic_element(ctx) {
mounted = true; mounted = true;
} }
}, },
p(ctx, dirty) { p: noop,
svelte_element0_data = get_spread_update(svelte_element0_levels, [{ class: "inner" }]);
if ((/-/).test(span)) {
set_custom_element_data_map(svelte_element0, svelte_element0_data);
} else {
set_attributes(svelte_element0, svelte_element0_data);
}
svelte_element1_data = get_spread_update(svelte_element1_levels, [{ class: "outer" }]);
if ((/-/).test(a)) {
set_custom_element_data_map(svelte_element1, svelte_element1_data);
} else {
set_attributes(svelte_element1, svelte_element1_data);
}
},
d(detaching) { d(detaching) {
if (detaching) detach(svelte_element1); if (detaching) detach(svelte_element1);
mounted = false; mounted = false;
@ -95,44 +52,23 @@ function create_dynamic_element(ctx) {
} }
function create_fragment(ctx) { function create_fragment(ctx) {
let previous_tag = a;
let svelte_element_anchor;
let svelte_element = a && create_dynamic_element(ctx); let svelte_element = a && create_dynamic_element(ctx);
return { return {
c() { c() {
if (svelte_element) svelte_element.c(); if (svelte_element) svelte_element.c();
svelte_element_anchor = empty();
}, },
m(target, anchor) { m(target, anchor) {
if (svelte_element) svelte_element.m(target, anchor); if (svelte_element) svelte_element.m(target, anchor);
insert(target, svelte_element_anchor, anchor);
}, },
p(ctx, [dirty]) { p(ctx, [dirty]) {
if (a) { if (a) {
if (!previous_tag) { svelte_element.p(ctx, dirty);
svelte_element = create_dynamic_element(ctx);
svelte_element.c();
svelte_element.m(svelte_element_anchor.parentNode, svelte_element_anchor);
} else if (safe_not_equal(previous_tag, a)) {
svelte_element.d(1);
svelte_element = create_dynamic_element(ctx);
svelte_element.c();
svelte_element.m(svelte_element_anchor.parentNode, svelte_element_anchor);
} else {
svelte_element.p(ctx, dirty);
}
} else if (previous_tag) {
svelte_element.d(1);
svelte_element = null;
} }
previous_tag = a;
}, },
i: noop, i: noop,
o: noop, o: noop,
d(detaching) { d(detaching) {
if (detaching) detach(svelte_element_anchor);
if (svelte_element) svelte_element.d(detaching); if (svelte_element) svelte_element.d(detaching);
} }
}; };

@ -2,10 +2,7 @@
import { import {
SvelteComponent, SvelteComponent,
append, append,
assign,
detach, detach,
empty,
get_spread_update,
init, init,
insert, insert,
noop, noop,
@ -17,37 +14,19 @@ import {
function create_dynamic_element(ctx) { function create_dynamic_element(ctx) {
let svelte_element1; let svelte_element1;
let svelte_element0; let svelte_element0;
let svelte_element0_levels = [{ xmlns: "http://www.w3.org/2000/svg" }];
let svelte_element0_data = {};
for (let i = 0; i < svelte_element0_levels.length; i += 1) {
svelte_element0_data = assign(svelte_element0_data, svelte_element0_levels[i]);
}
let svelte_element1_levels = [{ xmlns: "http://www.w3.org/2000/svg" }];
let svelte_element1_data = {};
for (let i = 0; i < svelte_element1_levels.length; i += 1) {
svelte_element1_data = assign(svelte_element1_data, svelte_element1_levels[i]);
}
return { return {
c() { c() {
svelte_element1 = svg_element(/*tag*/ ctx[0].svg); svelte_element1 = svg_element(/*tag*/ ctx[0].svg);
svelte_element0 = svg_element(/*tag*/ ctx[0].path); svelte_element0 = svg_element(/*tag*/ ctx[0].path);
set_svg_attributes(svelte_element0, svelte_element0_data); set_svg_attributes(svelte_element0, { xmlns: "http://www.w3.org/2000/svg" });
set_svg_attributes(svelte_element1, svelte_element1_data); set_svg_attributes(svelte_element1, { xmlns: "http://www.w3.org/2000/svg" });
}, },
m(target, anchor) { m(target, anchor) {
insert(target, svelte_element1, anchor); insert(target, svelte_element1, anchor);
append(svelte_element1, svelte_element0); append(svelte_element1, svelte_element0);
}, },
p(ctx, dirty) { p: noop,
svelte_element0_data = get_spread_update(svelte_element0_levels, [{ xmlns: "http://www.w3.org/2000/svg" }]);
set_svg_attributes(svelte_element0, svelte_element0_data);
svelte_element1_data = get_spread_update(svelte_element1_levels, [{ xmlns: "http://www.w3.org/2000/svg" }]);
set_svg_attributes(svelte_element1, svelte_element1_data);
},
d(detaching) { d(detaching) {
if (detaching) detach(svelte_element1); if (detaching) detach(svelte_element1);
} }
@ -55,44 +34,23 @@ function create_dynamic_element(ctx) {
} }
function create_fragment(ctx) { function create_fragment(ctx) {
let previous_tag = /*tag*/ ctx[0].svg;
let svelte_element_anchor;
let svelte_element = /*tag*/ ctx[0].svg && create_dynamic_element(ctx); let svelte_element = /*tag*/ ctx[0].svg && create_dynamic_element(ctx);
return { return {
c() { c() {
if (svelte_element) svelte_element.c(); if (svelte_element) svelte_element.c();
svelte_element_anchor = empty();
}, },
m(target, anchor) { m(target, anchor) {
if (svelte_element) svelte_element.m(target, anchor); if (svelte_element) svelte_element.m(target, anchor);
insert(target, svelte_element_anchor, anchor);
}, },
p(ctx, [dirty]) { p(ctx, [dirty]) {
if (/*tag*/ ctx[0].svg) { if (/*tag*/ ctx[0].svg) {
if (!previous_tag) { svelte_element.p(ctx, dirty);
svelte_element = create_dynamic_element(ctx);
svelte_element.c();
svelte_element.m(svelte_element_anchor.parentNode, svelte_element_anchor);
} else if (safe_not_equal(previous_tag, /*tag*/ ctx[0].svg)) {
svelte_element.d(1);
svelte_element = create_dynamic_element(ctx);
svelte_element.c();
svelte_element.m(svelte_element_anchor.parentNode, svelte_element_anchor);
} else {
svelte_element.p(ctx, dirty);
}
} else if (previous_tag) {
svelte_element.d(1);
svelte_element = null;
} }
previous_tag = /*tag*/ ctx[0].svg;
}, },
i: noop, i: noop,
o: noop, o: noop,
d(detaching) { d(detaching) {
if (detaching) detach(svelte_element_anchor);
if (svelte_element) svelte_element.d(detaching); if (svelte_element) svelte_element.d(detaching);
} }
}; };

@ -0,0 +1,244 @@
/* generated by Svelte vX.Y.Z */
import {
SvelteComponent,
assign,
detach,
element,
empty,
get_spread_update,
init,
insert,
noop,
safe_not_equal,
set_dynamic_element_data,
space,
toggle_class
} from "svelte/internal";
function create_dynamic_element_3(ctx) {
let svelte_element;
return {
c() {
svelte_element = element(static_value);
set_dynamic_element_data(static_value)(svelte_element, { static_value, ...static_obj });
toggle_class(svelte_element, "foo", static_value);
},
m(target, anchor) {
insert(target, svelte_element, anchor);
},
p: noop,
d(detaching) {
if (detaching) detach(svelte_element);
}
};
}
// (10:0) <svelte:element this={dynamic_value} {static_value} {...static_obj} class:foo={static_value} />
function create_dynamic_element_2(ctx) {
let svelte_element;
return {
c() {
svelte_element = element(/*dynamic_value*/ ctx[0]);
set_dynamic_element_data(/*dynamic_value*/ ctx[0])(svelte_element, { static_value, ...static_obj });
toggle_class(svelte_element, "foo", static_value);
},
m(target, anchor) {
insert(target, svelte_element, anchor);
},
p: noop,
d(detaching) {
if (detaching) detach(svelte_element);
}
};
}
// (12:0) <svelte:element this={static_value} {dynamic_value} {...dynamic_obj} class:foo={dynamic_value} />
function create_dynamic_element_1(ctx) {
let svelte_element;
let svelte_element_levels = [{ dynamic_value: /*dynamic_value*/ ctx[0] }, /*dynamic_obj*/ ctx[1]];
let svelte_element_data = {};
for (let i = 0; i < svelte_element_levels.length; i += 1) {
svelte_element_data = assign(svelte_element_data, svelte_element_levels[i]);
}
return {
c() {
svelte_element = element(static_value);
set_dynamic_element_data(static_value)(svelte_element, svelte_element_data);
toggle_class(svelte_element, "foo", /*dynamic_value*/ ctx[0]);
},
m(target, anchor) {
insert(target, svelte_element, anchor);
},
p(ctx, dirty) {
set_dynamic_element_data(static_value)(svelte_element, svelte_element_data = get_spread_update(svelte_element_levels, [
dirty & /*dynamic_value*/ 1 && { dynamic_value: /*dynamic_value*/ ctx[0] },
dirty & /*dynamic_obj*/ 2 && /*dynamic_obj*/ ctx[1]
]));
toggle_class(svelte_element, "foo", /*dynamic_value*/ ctx[0]);
},
d(detaching) {
if (detaching) detach(svelte_element);
}
};
}
// (14:0) <svelte:element this={dynamic_value} {dynamic_value} {...dynamic_obj} class:foo={dynamic_value} />
function create_dynamic_element(ctx) {
let svelte_element;
let svelte_element_levels = [{ dynamic_value: /*dynamic_value*/ ctx[0] }, /*dynamic_obj*/ ctx[1]];
let svelte_element_data = {};
for (let i = 0; i < svelte_element_levels.length; i += 1) {
svelte_element_data = assign(svelte_element_data, svelte_element_levels[i]);
}
return {
c() {
svelte_element = element(/*dynamic_value*/ ctx[0]);
set_dynamic_element_data(/*dynamic_value*/ ctx[0])(svelte_element, svelte_element_data);
toggle_class(svelte_element, "foo", /*dynamic_value*/ ctx[0]);
},
m(target, anchor) {
insert(target, svelte_element, anchor);
},
p(ctx, dirty) {
set_dynamic_element_data(/*dynamic_value*/ ctx[0])(svelte_element, svelte_element_data = get_spread_update(svelte_element_levels, [
dirty & /*dynamic_value*/ 1 && { dynamic_value: /*dynamic_value*/ ctx[0] },
dirty & /*dynamic_obj*/ 2 && /*dynamic_obj*/ ctx[1]
]));
toggle_class(svelte_element, "foo", /*dynamic_value*/ ctx[0]);
},
d(detaching) {
if (detaching) detach(svelte_element);
}
};
}
function create_fragment(ctx) {
let t0;
let previous_tag = /*dynamic_value*/ ctx[0];
let t1;
let t2;
let previous_tag_1 = /*dynamic_value*/ ctx[0];
let svelte_element3_anchor;
let svelte_element0 = static_value && create_dynamic_element_3(ctx);
let svelte_element1 = /*dynamic_value*/ ctx[0] && create_dynamic_element_2(ctx);
let svelte_element2 = static_value && create_dynamic_element_1(ctx);
let svelte_element3 = /*dynamic_value*/ ctx[0] && create_dynamic_element(ctx);
return {
c() {
if (svelte_element0) svelte_element0.c();
t0 = space();
if (svelte_element1) svelte_element1.c();
t1 = space();
if (svelte_element2) svelte_element2.c();
t2 = space();
if (svelte_element3) svelte_element3.c();
svelte_element3_anchor = empty();
},
m(target, anchor) {
if (svelte_element0) svelte_element0.m(target, anchor);
insert(target, t0, anchor);
if (svelte_element1) svelte_element1.m(target, anchor);
insert(target, t1, anchor);
if (svelte_element2) svelte_element2.m(target, anchor);
insert(target, t2, anchor);
if (svelte_element3) svelte_element3.m(target, anchor);
insert(target, svelte_element3_anchor, anchor);
},
p(ctx, [dirty]) {
if (static_value) {
svelte_element0.p(ctx, dirty);
}
if (/*dynamic_value*/ ctx[0]) {
if (!previous_tag) {
svelte_element1 = create_dynamic_element_2(ctx);
previous_tag = /*dynamic_value*/ ctx[0];
svelte_element1.c();
svelte_element1.m(t1.parentNode, t1);
} else if (safe_not_equal(previous_tag, /*dynamic_value*/ ctx[0])) {
svelte_element1.d(1);
svelte_element1 = create_dynamic_element_2(ctx);
previous_tag = /*dynamic_value*/ ctx[0];
svelte_element1.c();
svelte_element1.m(t1.parentNode, t1);
} else {
svelte_element1.p(ctx, dirty);
}
} else if (previous_tag) {
svelte_element1.d(1);
svelte_element1 = null;
previous_tag = /*dynamic_value*/ ctx[0];
}
if (static_value) {
svelte_element2.p(ctx, dirty);
}
if (/*dynamic_value*/ ctx[0]) {
if (!previous_tag_1) {
svelte_element3 = create_dynamic_element(ctx);
previous_tag_1 = /*dynamic_value*/ ctx[0];
svelte_element3.c();
svelte_element3.m(svelte_element3_anchor.parentNode, svelte_element3_anchor);
} else if (safe_not_equal(previous_tag_1, /*dynamic_value*/ ctx[0])) {
svelte_element3.d(1);
svelte_element3 = create_dynamic_element(ctx);
previous_tag_1 = /*dynamic_value*/ ctx[0];
svelte_element3.c();
svelte_element3.m(svelte_element3_anchor.parentNode, svelte_element3_anchor);
} else {
svelte_element3.p(ctx, dirty);
}
} else if (previous_tag_1) {
svelte_element3.d(1);
svelte_element3 = null;
previous_tag_1 = /*dynamic_value*/ ctx[0];
}
},
i: noop,
o: noop,
d(detaching) {
if (svelte_element0) svelte_element0.d(detaching);
if (detaching) detach(t0);
if (svelte_element1) svelte_element1.d(detaching);
if (detaching) detach(t1);
if (svelte_element2) svelte_element2.d(detaching);
if (detaching) detach(t2);
if (detaching) detach(svelte_element3_anchor);
if (svelte_element3) svelte_element3.d(detaching);
}
};
}
let static_value = 'a';
function instance($$self, $$props, $$invalidate) {
let { dynamic_value } = $$props;
let { dynamic_obj } = $$props;
let static_obj = {};
$$self.$$set = $$props => {
if ('dynamic_value' in $$props) $$invalidate(0, dynamic_value = $$props.dynamic_value);
if ('dynamic_obj' in $$props) $$invalidate(1, dynamic_obj = $$props.dynamic_obj);
};
return [dynamic_value, dynamic_obj, static_obj];
}
class Component extends SvelteComponent {
constructor(options) {
super();
init(this, options, instance, create_fragment, safe_not_equal, { dynamic_value: 0, dynamic_obj: 1 });
}
}
export default Component;

@ -0,0 +1,14 @@
<script>
export let dynamic_value;
export let dynamic_obj;
let static_value = 'a';
let static_obj = {};
</script>
<svelte:element this={static_value} {static_value} {...static_obj} class:foo={static_value} />
<svelte:element this={dynamic_value} {static_value} {...static_obj} class:foo={static_value} />
<svelte:element this={static_value} {dynamic_value} {...dynamic_obj} class:foo={dynamic_value} />
<svelte:element this={dynamic_value} {dynamic_value} {...dynamic_obj} class:foo={dynamic_value} />

@ -30,23 +30,31 @@ function create_fragment(ctx) {
video_updating = true; video_updating = true;
} }
/*video_timeupdate_handler*/ ctx[4].call(video); /*video_timeupdate_handler*/ ctx[5].call(video);
} }
return { return {
c() { c() {
video = element("video"); video = element("video");
if (/*videoHeight*/ ctx[1] === void 0 || /*videoWidth*/ ctx[2] === void 0) add_render_callback(() => /*video_resize_handler*/ ctx[5].call(video)); if (/*videoHeight*/ ctx[1] === void 0 || /*videoWidth*/ ctx[2] === void 0) add_render_callback(() => /*video_resize_handler*/ ctx[6].call(video));
add_render_callback(() => /*video_elementresize_handler*/ ctx[6].call(video)); add_render_callback(() => /*video_elementresize_handler*/ ctx[7].call(video));
if (/*readyState*/ ctx[4] === void 0) add_render_callback(() => /*video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[8].call(video));
}, },
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[6].bind(video)); video_resize_listener = add_resize_listener(video, /*video_elementresize_handler*/ ctx[7].bind(video));
if (!mounted) { if (!mounted) {
dispose = [ dispose = [
listen(video, "timeupdate", video_timeupdate_handler), listen(video, "timeupdate", video_timeupdate_handler),
listen(video, "resize", /*video_resize_handler*/ ctx[5]) listen(video, "resize", /*video_resize_handler*/ ctx[6]),
listen(video, "loadedmetadata", /*video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[8]),
listen(video, "loadeddata", /*video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[8]),
listen(video, "canplay", /*video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[8]),
listen(video, "canplaythrough", /*video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[8]),
listen(video, "playing", /*video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[8]),
listen(video, "waiting", /*video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[8]),
listen(video, "emptied", /*video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[8])
]; ];
mounted = true; mounted = true;
@ -75,6 +83,7 @@ function instance($$self, $$props, $$invalidate) {
let { videoHeight } = $$props; let { videoHeight } = $$props;
let { videoWidth } = $$props; let { videoWidth } = $$props;
let { offsetWidth } = $$props; let { offsetWidth } = $$props;
let { readyState } = $$props;
function video_timeupdate_handler() { function video_timeupdate_handler() {
currentTime = this.currentTime; currentTime = this.currentTime;
@ -93,11 +102,17 @@ function instance($$self, $$props, $$invalidate) {
$$invalidate(3, offsetWidth); $$invalidate(3, offsetWidth);
} }
function video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler() {
readyState = this.readyState;
$$invalidate(4, readyState);
}
$$self.$$set = $$props => { $$self.$$set = $$props => {
if ('currentTime' in $$props) $$invalidate(0, currentTime = $$props.currentTime); if ('currentTime' in $$props) $$invalidate(0, currentTime = $$props.currentTime);
if ('videoHeight' in $$props) $$invalidate(1, videoHeight = $$props.videoHeight); if ('videoHeight' in $$props) $$invalidate(1, videoHeight = $$props.videoHeight);
if ('videoWidth' in $$props) $$invalidate(2, videoWidth = $$props.videoWidth); if ('videoWidth' in $$props) $$invalidate(2, videoWidth = $$props.videoWidth);
if ('offsetWidth' in $$props) $$invalidate(3, offsetWidth = $$props.offsetWidth); if ('offsetWidth' in $$props) $$invalidate(3, offsetWidth = $$props.offsetWidth);
if ('readyState' in $$props) $$invalidate(4, readyState = $$props.readyState);
}; };
return [ return [
@ -105,9 +120,11 @@ function instance($$self, $$props, $$invalidate) {
videoHeight, videoHeight,
videoWidth, videoWidth,
offsetWidth, offsetWidth,
readyState,
video_timeupdate_handler, video_timeupdate_handler,
video_resize_handler, video_resize_handler,
video_elementresize_handler video_elementresize_handler,
video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler
]; ];
} }
@ -119,7 +136,8 @@ class Component extends SvelteComponent {
currentTime: 0, currentTime: 0,
videoHeight: 1, videoHeight: 1,
videoWidth: 2, videoWidth: 2,
offsetWidth: 3 offsetWidth: 3,
readyState: 4
}); });
} }
} }

@ -3,6 +3,7 @@
export let videoHeight; export let videoHeight;
export let videoWidth; export let videoWidth;
export let offsetWidth; export let offsetWidth;
export let readyState;
</script> </script>
<video bind:currentTime bind:videoHeight bind:videoWidth bind:offsetWidth/> <video bind:currentTime bind:videoHeight bind:videoWidth bind:offsetWidth bind:readyState/>

@ -0,0 +1,6 @@
export default {
async test({ assert, component }) {
assert.equal(component.toggle, true);
assert.equal(component.offsetHeight, 800);
}
};

@ -0,0 +1,18 @@
<script>
export let offsetHeight;
export let offsetWidth;
export let toggle = false;
$: if (offsetWidth) {
toggle = true;
}
</script>
<div class:toggle>
<div bind:offsetHeight bind:offsetWidth>{offsetHeight}</div>
</div>
<style>
.toggle > div {
height: 800px;
}
</style>

@ -0,0 +1,27 @@
<script>
import { afterUpdate, onDestroy } from "svelte";
export let id;
export let items;
let item = $items[id];
let selected = true;
function onClick() {
selected = !selected;
items.set({});
}
onDestroy(() => {
console.log("onDestroy");
});
afterUpdate(() => {
console.log("afterUpdate");
});
</script>
<button on:click="{onClick}">Click Me</button>
{#if selected}
<div>{item.id}</div>
{/if}

@ -0,0 +1,16 @@
export default {
html: `
<button>Click Me</button>
<div>1</div>
`,
async test({ assert, target, window }) {
const button = target.querySelector('button');
const event = new window.MouseEvent('click');
const messages = [];
const log = console.log;
console.log = msg => messages.push(msg);
await button.dispatchEvent(event);
console.log = log;
assert.deepEqual(messages, ['afterUpdate', 'onDestroy']);
}
};

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

Loading…
Cancel
Save