Merge branch 'master' into pr/4860

pull/4860/head
Conduitry 5 years ago
commit b80407d45d

@ -24,7 +24,10 @@ jobs:
- uses: actions/setup-node@v1
- run: 'npm i && npm run lint'
Unit:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1

@ -1,5 +1,15 @@
# Svelte changelog
## Unreleased
* Update `<select>` with `bind:value` when the available `<option>`s change ([#1764](https://github.com/sveltejs/svelte/issues/1764))
* Fix inconsistencies when setting a two-way bound `<input>` to `undefined` ([#3569](https://github.com/sveltejs/svelte/issues/3569))
* Fix setting `<select multiple>` when there are spread attributes ([#4392](https://github.com/sveltejs/svelte/issues/4392))
* Fix resize listening on certain older browsers ([#4752](https://github.com/sveltejs/svelte/issues/4752))
* Add `a11y-no-onchange` warning ([#4788](https://github.com/sveltejs/svelte/pull/4788))
* Add `muted` binding for media elements ([#2998](https://github.com/sveltejs/svelte/issues/2998))
* Fix let-less `<slot>` with context overflow ([#4624](https://github.com/sveltejs/svelte/issues/4624))
## 3.22.3
* Support default values and trailing commas in destructuring `{#await}` ([#4560](https://github.com/sveltejs/svelte/issues/4560), [#4810](https://github.com/sveltejs/svelte/issues/4810))

@ -77,6 +77,9 @@ npm install && npm run update
npm run dev
```
### Is svelte.dev down?
Probably not, but it's possible. If you can't seem to access any `.dev` sites, check out [this SuperUser question and answer](https://superuser.com/q/1413402).
## License

@ -121,6 +121,9 @@ An element or component can have multiple spread attributes, interspersed with r
<input {...$$restProps}>
```
> The `value` attribute of an `input` element or its children `option` elements must not be set with spread attributes when using `bind:group` or `bind:checked`. Svelte needs to be able to see the element's `value` directly in the markup in these cases so that it can link it to the bound variable.
---
### Text expressions
@ -603,12 +606,13 @@ Media elements (`<audio>` and `<video>`) have their own set of bindings — six
* `seeking` (readonly) — boolean
* `ended` (readonly) — boolean
...and four *two-way* bindings:
...and five *two-way* bindings:
* `currentTime` — the current point in the video, in seconds
* `playbackRate` — how fast to play the video, where 1 is 'normal'
* `paused` — this one should be self-explanatory
* `volume` — a value between 0 and 1
* `muted` — a boolean value where true is muted
Videos additionally have readonly `videoWidth` and `videoHeight` bindings.
@ -624,6 +628,7 @@ Videos additionally have readonly `videoWidth` and `videoHeight` bindings.
bind:currentTime
bind:paused
bind:volume
bind:muted
bind:videoWidth
bind:videoHeight
></video>

@ -287,7 +287,9 @@ The second argument to `readable` is the same as the second argument to `writabl
```js
import { readable } from 'svelte/store';
const time = readable(new Date(), set => {
const time = readable(null, set => {
set(new Date());
const interval = setInterval(() => {
set(new Date());
}, 1000);
@ -521,7 +523,7 @@ $: $size = big ? 100 : 10;
### `svelte/transition`
The `svelte/transition` module exports six functions: `fade`, `fly`, `slide`, `scale`, `draw` and `crossfade`. They are for use with Svelte [`transitions`](docs#transition_fn).
The `svelte/transition` module exports seven functions: `fade`, `blur`, `fly`, `slide`, `scale`, `draw` and `crossfade`. They are for use with Svelte [`transitions`](docs#transition_fn).
#### `fade`

@ -1,5 +0,0 @@
---
question: Is svelte.dev down?
---
Probably not, but it's possible. If you can't seem to access any `.dev` sites, check out [this SuperUser question and answer](https://superuser.com/q/1413402).

@ -33,11 +33,12 @@ The complete set of bindings for `<audio>` and `<video>` is as follows — six *
* `seeking` (readonly) — boolean
* `ended` (readonly) — boolean
...and four *two-way* bindings:
...and five *two-way* bindings:
* `currentTime` — the current point in the video, in seconds
* `playbackRate` — how fast to play the video, where `1` is 'normal'
* `paused` — this one should be self-explanatory
* `volume` — a value between 0 and 1
* `muted` — a boolean value where true is muted
Videos additionally have readonly `videoWidth` and `videoHeight` bindings.

@ -638,6 +638,7 @@ export default class Element extends Node {
name === 'seekable' ||
name === 'played' ||
name === 'volume' ||
name === 'muted' ||
name === 'playbackRate' ||
name === 'seeking' ||
name === 'ended'

@ -102,25 +102,12 @@ export default class AttributeWrapper {
} else if (is_select_value_attribute) {
// annoying special case
const is_multiple_select = element.node.get_static_attribute_value('multiple');
const i = block.get_unique_name('i');
const option = block.get_unique_name('option');
const if_statement = is_multiple_select
? b`
${option}.selected = ~${last}.indexOf(${option}.__value);`
: b`
if (${option}.__value === ${last}) {
${option}.selected = true;
${{ type: 'BreakStatement' }};
}`; // TODO the BreakStatement is gross, but it's unsyntactic otherwise...
updater = b`
for (var ${i} = 0; ${i} < ${element.var}.options.length; ${i} += 1) {
var ${option} = ${element.var}.options[${i}];
${if_statement}
}
`;
if (is_multiple_select) {
updater = b`@select_options(${element.var}, ${last});`;
} else {
updater = b`@select_option(${element.var}, ${last});`;
}
block.chunks.mount.push(b`
${last} = ${value};

@ -87,7 +87,7 @@ export default class BindingWrapper {
const update_conditions: any[] = this.needs_lock ? [x`!${lock}`] : [];
const mount_conditions: any[] = [];
const dependency_array = [...this.node.expression.dependencies];
const dependency_array = Array.from(this.get_dependencies());
if (dependency_array.length > 0) {
update_conditions.push(block.renderer.dirty(dependency_array));

@ -93,7 +93,7 @@ const events = [
event_names: ['volumechange'],
filter: (node: Element, name: string) =>
node.is_media_node() &&
name === 'volume'
(name === 'volume' || name === 'muted')
},
{
event_names: ['ratechange'],
@ -705,10 +705,27 @@ export default class ElementWrapper extends Wrapper {
);
block.chunks.update.push(b`
${fn}(${this.var}, @get_spread_update(${levels}, [
${fn}(${this.var}, ${data} = @get_spread_update(${levels}, [
${updates}
]));
`);
// handle edge cases for elements
if (this.node.name === 'select') {
const dependencies = new Set();
for (const attr of this.attributes) {
for (const dep of attr.node.dependencies) {
dependencies.add(dep);
}
}
block.chunks.mount.push(b`
if (${data}.multiple) @select_options(${this.var}, ${data}.value);
`);
block.chunks.update.push(b`
if (${block.renderer.dirty(Array.from(dependencies))} && ${data}.multiple) @select_options(${this.var}, ${data}.value);
`);
}
}
add_transitions(

@ -172,10 +172,7 @@ export default class SlotWrapper extends Wrapper {
const slot_update = b`
if (${slot}.p && ${renderer.dirty(dynamic_dependencies)}) {
${slot}.p(
@get_slot_context(${slot_definition}, #ctx, ${renderer.reference('$$scope')}, ${get_slot_context_fn}),
@get_slot_changes(${slot_definition}, ${renderer.reference('$$scope')}, #dirty, ${get_slot_changes_fn})
);
@update_slot(${slot}, ${slot_definition}, #ctx, ${renderer.reference('$$scope')}, #dirty, ${get_slot_changes_fn}, ${get_slot_context_fn});
}
`;
const fallback_update = has_fallback && fallback_dynamic_dependencies.length > 0 && b`

@ -103,6 +103,14 @@ export function get_slot_changes(definition, $$scope, dirty, fn) {
return $$scope.dirty;
}
export function update_slot(slot, slot_definition, ctx, $$scope, dirty, get_slot_changes_fn, get_slot_context_fn) {
const slot_changes = get_slot_changes(slot_definition, $$scope, dirty, get_slot_changes_fn);
if (slot_changes) {
const slot_context = get_slot_context(slot_definition, ctx, $$scope, get_slot_context_fn);
slot.p(slot_context, slot_changes);
}
}
export function exclude_internal_props(props) {
const result = {};
for (const k in props) if (k[0] !== '$') result[k] = props[k];

@ -30,18 +30,18 @@ function create_fragment(ctx) {
audio_updating = true;
}
/*audio_timeupdate_handler*/ ctx[12].call(audio);
/*audio_timeupdate_handler*/ ctx[13].call(audio);
}
return {
c() {
audio = element("audio");
if (/*buffered*/ ctx[0] === void 0) add_render_callback(() => /*audio_progress_handler*/ ctx[10].call(audio));
if (/*buffered*/ ctx[0] === void 0 || /*seekable*/ ctx[1] === void 0) add_render_callback(() => /*audio_loadedmetadata_handler*/ ctx[11].call(audio));
if (/*played*/ ctx[2] === void 0 || /*currentTime*/ ctx[3] === void 0 || /*ended*/ ctx[9] === void 0) add_render_callback(audio_timeupdate_handler);
if (/*duration*/ ctx[4] === void 0) add_render_callback(() => /*audio_durationchange_handler*/ ctx[13].call(audio));
if (/*seeking*/ ctx[8] === void 0) add_render_callback(() => /*audio_seeking_seeked_handler*/ ctx[17].call(audio));
if (/*ended*/ ctx[9] === void 0) add_render_callback(() => /*audio_ended_handler*/ ctx[18].call(audio));
if (/*buffered*/ ctx[0] === void 0) add_render_callback(() => /*audio_progress_handler*/ ctx[11].call(audio));
if (/*buffered*/ ctx[0] === void 0 || /*seekable*/ ctx[1] === void 0) add_render_callback(() => /*audio_loadedmetadata_handler*/ ctx[12].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 (/*duration*/ ctx[4] === void 0) add_render_callback(() => /*audio_durationchange_handler*/ ctx[14].call(audio));
if (/*seeking*/ ctx[9] === void 0) add_render_callback(() => /*audio_seeking_seeked_handler*/ ctx[18].call(audio));
if (/*ended*/ ctx[10] === void 0) add_render_callback(() => /*audio_ended_handler*/ ctx[19].call(audio));
},
m(target, anchor) {
insert(target, audio, anchor);
@ -50,23 +50,25 @@ function create_fragment(ctx) {
audio.volume = /*volume*/ ctx[6];
}
if (!isNaN(/*playbackRate*/ ctx[7])) {
audio.playbackRate = /*playbackRate*/ ctx[7];
audio.muted = /*muted*/ ctx[7];
if (!isNaN(/*playbackRate*/ ctx[8])) {
audio.playbackRate = /*playbackRate*/ ctx[8];
}
if (!mounted) {
dispose = [
listen(audio, "progress", /*audio_progress_handler*/ ctx[10]),
listen(audio, "loadedmetadata", /*audio_loadedmetadata_handler*/ ctx[11]),
listen(audio, "progress", /*audio_progress_handler*/ ctx[11]),
listen(audio, "loadedmetadata", /*audio_loadedmetadata_handler*/ ctx[12]),
listen(audio, "timeupdate", audio_timeupdate_handler),
listen(audio, "durationchange", /*audio_durationchange_handler*/ ctx[13]),
listen(audio, "play", /*audio_play_pause_handler*/ ctx[14]),
listen(audio, "pause", /*audio_play_pause_handler*/ ctx[14]),
listen(audio, "volumechange", /*audio_volumechange_handler*/ ctx[15]),
listen(audio, "ratechange", /*audio_ratechange_handler*/ ctx[16]),
listen(audio, "seeking", /*audio_seeking_seeked_handler*/ ctx[17]),
listen(audio, "seeked", /*audio_seeking_seeked_handler*/ ctx[17]),
listen(audio, "ended", /*audio_ended_handler*/ ctx[18])
listen(audio, "durationchange", /*audio_durationchange_handler*/ ctx[14]),
listen(audio, "play", /*audio_play_pause_handler*/ ctx[15]),
listen(audio, "pause", /*audio_play_pause_handler*/ ctx[15]),
listen(audio, "volumechange", /*audio_volumechange_handler*/ ctx[16]),
listen(audio, "ratechange", /*audio_ratechange_handler*/ ctx[17]),
listen(audio, "seeking", /*audio_seeking_seeked_handler*/ ctx[18]),
listen(audio, "seeked", /*audio_seeking_seeked_handler*/ ctx[18]),
listen(audio, "ended", /*audio_ended_handler*/ ctx[19])
];
mounted = true;
@ -87,8 +89,12 @@ function create_fragment(ctx) {
audio.volume = /*volume*/ ctx[6];
}
if (dirty & /*playbackRate*/ 128 && !isNaN(/*playbackRate*/ ctx[7])) {
audio.playbackRate = /*playbackRate*/ ctx[7];
if (dirty & /*muted*/ 128) {
audio.muted = /*muted*/ ctx[7];
}
if (dirty & /*playbackRate*/ 256 && !isNaN(/*playbackRate*/ ctx[8])) {
audio.playbackRate = /*playbackRate*/ ctx[8];
}
},
i: noop,
@ -109,6 +115,7 @@ function instance($$self, $$props, $$invalidate) {
let { duration } = $$props;
let { paused } = $$props;
let { volume } = $$props;
let { muted } = $$props;
let { playbackRate } = $$props;
let { seeking } = $$props;
let { ended } = $$props;
@ -131,7 +138,7 @@ function instance($$self, $$props, $$invalidate) {
ended = this.ended;
$$invalidate(2, played);
$$invalidate(3, currentTime);
$$invalidate(9, ended);
$$invalidate(10, ended);
}
function audio_durationchange_handler() {
@ -146,22 +153,24 @@ function instance($$self, $$props, $$invalidate) {
function audio_volumechange_handler() {
volume = this.volume;
muted = this.muted;
$$invalidate(6, volume);
$$invalidate(7, muted);
}
function audio_ratechange_handler() {
playbackRate = this.playbackRate;
$$invalidate(7, playbackRate);
$$invalidate(8, playbackRate);
}
function audio_seeking_seeked_handler() {
seeking = this.seeking;
$$invalidate(8, seeking);
$$invalidate(9, seeking);
}
function audio_ended_handler() {
ended = this.ended;
$$invalidate(9, ended);
$$invalidate(10, ended);
}
$$self.$set = $$props => {
@ -172,9 +181,10 @@ function instance($$self, $$props, $$invalidate) {
if ("duration" in $$props) $$invalidate(4, duration = $$props.duration);
if ("paused" in $$props) $$invalidate(5, paused = $$props.paused);
if ("volume" in $$props) $$invalidate(6, volume = $$props.volume);
if ("playbackRate" in $$props) $$invalidate(7, playbackRate = $$props.playbackRate);
if ("seeking" in $$props) $$invalidate(8, seeking = $$props.seeking);
if ("ended" in $$props) $$invalidate(9, ended = $$props.ended);
if ("muted" in $$props) $$invalidate(7, muted = $$props.muted);
if ("playbackRate" in $$props) $$invalidate(8, playbackRate = $$props.playbackRate);
if ("seeking" in $$props) $$invalidate(9, seeking = $$props.seeking);
if ("ended" in $$props) $$invalidate(10, ended = $$props.ended);
};
return [
@ -185,6 +195,7 @@ function instance($$self, $$props, $$invalidate) {
duration,
paused,
volume,
muted,
playbackRate,
seeking,
ended,
@ -212,9 +223,10 @@ class Component extends SvelteComponent {
duration: 4,
paused: 5,
volume: 6,
playbackRate: 7,
seeking: 8,
ended: 9
muted: 7,
playbackRate: 8,
seeking: 9,
ended: 10
});
}
}

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

@ -7,7 +7,8 @@ import {
init,
insert,
noop,
safe_not_equal
safe_not_equal,
select_option
} from "svelte/internal";
function create_fragment(ctx) {
@ -33,26 +34,11 @@ function create_fragment(ctx) {
append(select, option0);
append(select, option1);
select_value_value = /*current*/ ctx[0];
for (var i = 0; i < select.options.length; i += 1) {
var option = select.options[i];
if (option.__value === select_value_value) {
option.selected = true;
break;
}
}
select_option(select, select_value_value);
},
p(ctx, [dirty]) {
if (dirty & /*current*/ 1 && select_value_value !== (select_value_value = /*current*/ ctx[0])) {
for (var i = 0; i < select.options.length; i += 1) {
var option = select.options[i];
if (option.__value === select_value_value) {
option.selected = true;
break;
}
}
select_option(select, select_value_value);
}
},
i: noop,

@ -8,6 +8,7 @@ export default {
assert.equal(component.t, 0);
assert.equal(component.d, 0);
assert.equal(component.v, 0.5);
assert.equal(component.m, true);
assert.equal(component.r, 1);
assert.equal(component.paused, true);
@ -20,6 +21,7 @@ export default {
audio.currentTime = 10;
audio.duration = 20;
audio.volume = 0.75;
audio.muted = false;
audio.playbackRate = 2;
audio.dispatchEvent(timeupdate);
audio.dispatchEvent(durationchange);
@ -30,6 +32,7 @@ export default {
assert.equal(component.t, 10);
assert.equal(component.d, 0); // not 20, because read-only. Not sure how to test this!
assert.equal(component.v, 0.75);
assert.equal(component.m, false);
assert.equal(component.r, 2);
assert.equal(component.paused, true); // ditto...
}

@ -4,7 +4,8 @@
export let paused;
export let v;
export let r;
export let m;
</script>
<audio bind:currentTime={t} bind:duration={d} bind:paused bind:volume={v} bind:playbackRate={r}
<audio bind:currentTime={t} bind:duration={d} bind:paused bind:volume={v} bind:muted={m} bind:playbackRate={r}
src='music.mp3'></audio>

@ -0,0 +1,34 @@
export default {
props: {
items: [],
selected: 'two'
},
html: `
<select></select>
<p>selected: two</p>
`,
ssrHtml: `
<select value="two"></select>
<p>selected: two</p>
`,
test({ assert, component, target }) {
component.items = [ 'one', 'two', 'three' ];
const options = target.querySelectorAll('option');
assert.ok(!options[0].selected);
assert.ok(options[1].selected);
assert.ok(!options[2].selected);
assert.htmlEqual(target.innerHTML, `
<select>
<option value='one'>one</option>
<option value='two'>two</option>
<option value='three'>three</option>
</select>
<p>selected: two</p>
`);
}
};

@ -0,0 +1,12 @@
<script>
export let selected;
export let items;
</script>
<select bind:value={selected}>
{#each items as item}
<option>{item}</option>
{/each}
</select>
<p>selected: {selected || 'nothing'}</p>

@ -0,0 +1,15 @@
<script>
let open = false;
function toggle() {
open = !open;
}
</script>
<div on:click={toggle}>
<slot name="target" {open}></slot>
<!-- This actually isn't necessary to reproduce. -->
{#if open}
<slot name="content"></slot>
{/if}
</div>

@ -0,0 +1,32 @@
// overflow bitmask + slot missing `let:`
export default {
html: `
<div>
<button slot="target">Toggle inside 1</button>
</div>
<button>Toggle outside</button>
`,
async test({ assert, component, target, window }) {
const button = target.querySelectorAll('button')[1];
const div = target.querySelector('div');
await div.dispatchEvent(new window.MouseEvent('click'));
assert.htmlEqual(target.innerHTML, `
<div>
<button slot="target">Toggle inside 1</button>
<div slot="content">Open</div>
</div>
<button>Toggle outside</button>
`);
await button.dispatchEvent(new window.MouseEvent('click'));
assert.htmlEqual(target.innerHTML, `
<div>
<button slot="target">Toggle inside 2</button>
<div slot="content">Open</div>
</div>
<button>Toggle outside</button>
`);
}
};

@ -0,0 +1,23 @@
<script>
import Slotted from './Slotted.svelte';
let lotsOfNumbers = Array.from({length: 50}, () => 1);
let [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab, ac, ad, ae, af, ag, ah] = lotsOfNumbers;
let last = 1;
function toggle () {
last = 2;
}
</script>
<Slotted>
<button slot="target">
Toggle inside {last}
</button>
<div slot="content">
Open
</div>
</Slotted>
<button on:click={toggle}>Toggle outside</button>

@ -0,0 +1,39 @@
export default {
async test({ assert, component, target, window }) {
const [input1, input2] = target.querySelectorAll('input');
const select = target.querySelector('select');
const [option1, option2] = select.childNodes;
let selections = Array.from(select.selectedOptions);
assert.equal(selections.length, 2);
assert.ok(selections.includes(option1));
assert.ok(selections.includes(option2));
const event = new window.Event('change');
input1.checked = false;
await input1.dispatchEvent(event);
selections = Array.from(select.selectedOptions);
assert.equal(selections.length, 1);
assert.ok(!selections.includes(option1));
assert.ok(selections.includes(option2));
input2.checked = false;
await input2.dispatchEvent(event);
input1.checked = true;
await input1.dispatchEvent(event);
selections = Array.from(select.selectedOptions);
assert.equal(selections.length, 1);
assert.ok(selections.includes(option1));
assert.ok(!selections.includes(option2));
component.spread = { value: ['Hello', 'World'] };
selections = Array.from(select.selectedOptions);
assert.equal(selections.length, 2);
assert.ok(selections.includes(option1));
assert.ok(selections.includes(option2));
}
};

@ -0,0 +1,12 @@
<script>
let value = ['Hello', 'World'];
export let spread = {};
</script>
<select multiple {value} {...spread}>
<option>Hello</option>
<option>World</option>
</select>
<input type="checkbox" value="Hello" bind:group={value}>
<input type="checkbox" value="World" bind:group={value}>
Loading…
Cancel
Save