Skip to content

Commit abe88f3

Browse files
colincaseyConduitry
authored andcommitted
FIX #2446: apply bindings and event handlers in order
1 parent fb6d570 commit abe88f3

File tree

5 files changed

+139
-58
lines changed

5 files changed

+139
-58
lines changed

src/compiler/compile/render_dom/wrappers/Element/index.ts

+54-20
Original file line numberDiff line numberDiff line change
@@ -377,8 +377,7 @@ export default class ElementWrapper extends Wrapper {
377377
}
378378

379379
this.add_attributes(block);
380-
this.add_bindings(block);
381-
this.add_event_handlers(block);
380+
this.add_directives_in_order(block);
382381
this.add_transitions(block);
383382
this.add_animation(block);
384383
this.add_actions(block);
@@ -436,29 +435,62 @@ export default class ElementWrapper extends Wrapper {
436435
return x`@claim_element(${nodes}, "${name}", { ${attributes} }, ${svg})`;
437436
}
438437

439-
add_bindings(block: Block) {
438+
add_directives_in_order (block: Block) {
439+
interface BindingGroup {
440+
events: string[];
441+
bindings: Binding[];
442+
}
443+
444+
const bindingGroups = events
445+
.map(event => ({
446+
events: event.event_names,
447+
bindings: this.bindings
448+
.filter(binding => binding.node.name !== 'this')
449+
.filter(binding => event.filter(this.node, binding.node.name))
450+
}))
451+
.filter(group => group.bindings.length);
452+
453+
const this_binding = this.bindings.find(b => b.node.name === 'this');
454+
455+
function getOrder (item: EventHandler | BindingGroup | Binding) {
456+
if (item instanceof EventHandler) {
457+
return item.node.start;
458+
} else if (item instanceof Binding) {
459+
return item.node.start;
460+
} else {
461+
return item.bindings[0].node.start;
462+
}
463+
}
464+
465+
const ordered: Array<EventHandler | BindingGroup | Binding> = [].concat(bindingGroups, this.event_handlers, this_binding).filter(Boolean);
466+
467+
ordered.sort((a, b) => getOrder(a) - getOrder(b));
468+
469+
ordered.forEach(bindingGroupOrEventHandler => {
470+
if (bindingGroupOrEventHandler instanceof EventHandler) {
471+
add_event_handlers(block, this.var, [bindingGroupOrEventHandler]);
472+
} else if (bindingGroupOrEventHandler instanceof Binding) {
473+
this.add_this_binding(block, bindingGroupOrEventHandler);
474+
} else {
475+
this.add_bindings(block, bindingGroupOrEventHandler);
476+
}
477+
});
478+
}
479+
480+
add_bindings(block: Block, bindingGroup) {
440481
const { renderer } = this;
441482

442-
if (this.bindings.length === 0) return;
483+
if (bindingGroup.bindings.length === 0) return;
443484

444485
renderer.component.has_reactive_assignments = true;
445486

446-
const lock = this.bindings.some(binding => binding.needs_lock) ?
487+
const lock = bindingGroup.bindings.some(binding => binding.needs_lock) ?
447488
block.get_unique_name(`${this.var.name}_updating`) :
448489
null;
449490

450491
if (lock) block.add_variable(lock, x`false`);
451492

452-
const groups = events
453-
.map(event => ({
454-
events: event.event_names,
455-
bindings: this.bindings
456-
.filter(binding => binding.node.name !== 'this')
457-
.filter(binding => event.filter(this.node, binding.node.name))
458-
}))
459-
.filter(group => group.bindings.length);
460-
461-
groups.forEach(group => {
493+
[bindingGroup].forEach(group => {
462494
const handler = renderer.component.get_unique_name(`${this.var.name}_${group.events.join('_')}_handler`);
463495
renderer.add_to_context(handler.name);
464496

@@ -586,13 +618,15 @@ export default class ElementWrapper extends Wrapper {
586618
if (lock) {
587619
block.chunks.update.push(b`${lock} = false;`);
588620
}
621+
}
589622

590-
const this_binding = this.bindings.find(b => b.node.name === 'this');
591-
if (this_binding) {
592-
const binding_callback = bind_this(renderer.component, block, this_binding.node, this.var);
623+
add_this_binding(block: Block, this_binding: Binding) {
624+
const { renderer } = this;
625+
626+
renderer.component.has_reactive_assignments = true;
593627

594-
block.chunks.mount.push(binding_callback);
595-
}
628+
const binding_callback = bind_this(renderer.component, block, this_binding.node, this.var);
629+
block.chunks.mount.push(binding_callback);
596630
}
597631

598632
add_attributes(block: Block) {

test/js/samples/media-bindings/expected.js

+25-25
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,26 @@ function create_fragment(ctx) {
2929
audio_updating = true;
3030
}
3131

32-
/*audio_timeupdate_handler*/ ctx[10].call(audio);
32+
/*audio_timeupdate_handler*/ ctx[12].call(audio);
3333
}
3434

3535
return {
3636
c() {
3737
audio = element("audio");
38+
if (/*buffered*/ ctx[0] === void 0) add_render_callback(() => /*audio_progress_handler*/ ctx[10].call(audio));
39+
if (/*buffered*/ ctx[0] === void 0 || /*seekable*/ ctx[1] === void 0) add_render_callback(() => /*audio_loadedmetadata_handler*/ ctx[11].call(audio));
3840
if (/*played*/ ctx[2] === void 0 || /*currentTime*/ ctx[3] === void 0 || /*ended*/ ctx[9] === void 0) add_render_callback(audio_timeupdate_handler);
39-
if (/*duration*/ ctx[4] === void 0) add_render_callback(() => /*audio_durationchange_handler*/ ctx[11].call(audio));
40-
if (/*buffered*/ ctx[0] === void 0) add_render_callback(() => /*audio_progress_handler*/ ctx[13].call(audio));
41-
if (/*buffered*/ ctx[0] === void 0 || /*seekable*/ ctx[1] === void 0) add_render_callback(() => /*audio_loadedmetadata_handler*/ ctx[14].call(audio));
41+
if (/*duration*/ ctx[4] === void 0) add_render_callback(() => /*audio_durationchange_handler*/ ctx[13].call(audio));
4242
if (/*seeking*/ ctx[8] === void 0) add_render_callback(() => /*audio_seeking_seeked_handler*/ ctx[17].call(audio));
4343
if (/*ended*/ ctx[9] === void 0) add_render_callback(() => /*audio_ended_handler*/ ctx[18].call(audio));
4444

4545
dispose = [
46+
listen(audio, "progress", /*audio_progress_handler*/ ctx[10]),
47+
listen(audio, "loadedmetadata", /*audio_loadedmetadata_handler*/ ctx[11]),
4648
listen(audio, "timeupdate", audio_timeupdate_handler),
47-
listen(audio, "durationchange", /*audio_durationchange_handler*/ ctx[11]),
48-
listen(audio, "play", /*audio_play_pause_handler*/ ctx[12]),
49-
listen(audio, "pause", /*audio_play_pause_handler*/ ctx[12]),
50-
listen(audio, "progress", /*audio_progress_handler*/ ctx[13]),
51-
listen(audio, "loadedmetadata", /*audio_loadedmetadata_handler*/ ctx[14]),
49+
listen(audio, "durationchange", /*audio_durationchange_handler*/ ctx[13]),
50+
listen(audio, "play", /*audio_play_pause_handler*/ ctx[14]),
51+
listen(audio, "pause", /*audio_play_pause_handler*/ ctx[14]),
5252
listen(audio, "volumechange", /*audio_volumechange_handler*/ ctx[15]),
5353
listen(audio, "ratechange", /*audio_ratechange_handler*/ ctx[16]),
5454
listen(audio, "seeking", /*audio_seeking_seeked_handler*/ ctx[17]),
@@ -72,6 +72,8 @@ function create_fragment(ctx) {
7272
audio.currentTime = /*currentTime*/ ctx[3];
7373
}
7474

75+
audio_updating = false;
76+
7577
if (dirty & /*paused*/ 32 && audio_is_paused !== (audio_is_paused = /*paused*/ ctx[5])) {
7678
audio[audio_is_paused ? "pause" : "play"]();
7779
}
@@ -83,8 +85,6 @@ function create_fragment(ctx) {
8385
if (dirty & /*playbackRate*/ 128 && !isNaN(/*playbackRate*/ ctx[7])) {
8486
audio.playbackRate = /*playbackRate*/ ctx[7];
8587
}
86-
87-
audio_updating = false;
8888
},
8989
i: noop,
9090
o: noop,
@@ -107,6 +107,18 @@ function instance($$self, $$props, $$invalidate) {
107107
let { seeking } = $$props;
108108
let { ended } = $$props;
109109

110+
function audio_progress_handler() {
111+
buffered = time_ranges_to_array(this.buffered);
112+
$$invalidate(0, buffered);
113+
}
114+
115+
function audio_loadedmetadata_handler() {
116+
buffered = time_ranges_to_array(this.buffered);
117+
seekable = time_ranges_to_array(this.seekable);
118+
$$invalidate(0, buffered);
119+
$$invalidate(1, seekable);
120+
}
121+
110122
function audio_timeupdate_handler() {
111123
played = time_ranges_to_array(this.played);
112124
currentTime = this.currentTime;
@@ -126,18 +138,6 @@ function instance($$self, $$props, $$invalidate) {
126138
$$invalidate(5, paused);
127139
}
128140

129-
function audio_progress_handler() {
130-
buffered = time_ranges_to_array(this.buffered);
131-
$$invalidate(0, buffered);
132-
}
133-
134-
function audio_loadedmetadata_handler() {
135-
buffered = time_ranges_to_array(this.buffered);
136-
seekable = time_ranges_to_array(this.seekable);
137-
$$invalidate(0, buffered);
138-
$$invalidate(1, seekable);
139-
}
140-
141141
function audio_volumechange_handler() {
142142
volume = this.volume;
143143
$$invalidate(6, volume);
@@ -182,11 +182,11 @@ function instance($$self, $$props, $$invalidate) {
182182
playbackRate,
183183
seeking,
184184
ended,
185+
audio_progress_handler,
186+
audio_loadedmetadata_handler,
185187
audio_timeupdate_handler,
186188
audio_durationchange_handler,
187189
audio_play_pause_handler,
188-
audio_progress_handler,
189-
audio_loadedmetadata_handler,
190190
audio_volumechange_handler,
191191
audio_ratechange_handler,
192192
audio_seeking_seeked_handler,

test/js/samples/video-bindings/expected.js

+13-13
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ import {
1717
function create_fragment(ctx) {
1818
let video;
1919
let video_updating = false;
20-
let video_resize_listener;
2120
let video_animationframe;
21+
let video_resize_listener;
2222
let dispose;
2323

2424
function video_timeupdate_handler() {
@@ -29,23 +29,23 @@ function create_fragment(ctx) {
2929
video_updating = true;
3030
}
3131

32-
/*video_timeupdate_handler*/ ctx[5].call(video);
32+
/*video_timeupdate_handler*/ ctx[4].call(video);
3333
}
3434

3535
return {
3636
c() {
3737
video = element("video");
38-
add_render_callback(() => /*video_elementresize_handler*/ ctx[4].call(video));
39-
if (/*videoHeight*/ ctx[1] === void 0 || /*videoWidth*/ ctx[2] === void 0) add_render_callback(() => /*video_resize_handler*/ ctx[6].call(video));
38+
if (/*videoHeight*/ ctx[1] === void 0 || /*videoWidth*/ ctx[2] === void 0) add_render_callback(() => /*video_resize_handler*/ ctx[5].call(video));
39+
add_render_callback(() => /*video_elementresize_handler*/ ctx[6].call(video));
4040

4141
dispose = [
4242
listen(video, "timeupdate", video_timeupdate_handler),
43-
listen(video, "resize", /*video_resize_handler*/ ctx[6])
43+
listen(video, "resize", /*video_resize_handler*/ ctx[5])
4444
];
4545
},
4646
m(target, anchor) {
4747
insert(target, video, anchor);
48-
video_resize_listener = add_resize_listener(video, /*video_elementresize_handler*/ ctx[4].bind(video));
48+
video_resize_listener = add_resize_listener(video, /*video_elementresize_handler*/ ctx[6].bind(video));
4949
},
5050
p(ctx, [dirty]) {
5151
if (!video_updating && dirty & /*currentTime*/ 1 && !isNaN(/*currentTime*/ ctx[0])) {
@@ -70,11 +70,6 @@ function instance($$self, $$props, $$invalidate) {
7070
let { videoWidth } = $$props;
7171
let { offsetWidth } = $$props;
7272

73-
function video_elementresize_handler() {
74-
offsetWidth = this.offsetWidth;
75-
$$invalidate(3, offsetWidth);
76-
}
77-
7873
function video_timeupdate_handler() {
7974
currentTime = this.currentTime;
8075
$$invalidate(0, currentTime);
@@ -87,6 +82,11 @@ function instance($$self, $$props, $$invalidate) {
8782
$$invalidate(2, videoWidth);
8883
}
8984

85+
function video_elementresize_handler() {
86+
offsetWidth = this.offsetWidth;
87+
$$invalidate(3, offsetWidth);
88+
}
89+
9090
$$self.$set = $$props => {
9191
if ("currentTime" in $$props) $$invalidate(0, currentTime = $$props.currentTime);
9292
if ("videoHeight" in $$props) $$invalidate(1, videoHeight = $$props.videoHeight);
@@ -99,9 +99,9 @@ function instance($$self, $$props, $$invalidate) {
9999
videoHeight,
100100
videoWidth,
101101
offsetWidth,
102-
video_elementresize_handler,
103102
video_timeupdate_handler,
104-
video_resize_handler
103+
video_resize_handler,
104+
video_elementresize_handler
105105
];
106106
}
107107

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
export default {
2+
props: {
3+
value: ''
4+
},
5+
6+
html: `
7+
<input>
8+
<p></p>
9+
`,
10+
11+
ssrHtml: `
12+
<input>
13+
<p></p>
14+
`,
15+
16+
async test({ assert, component, target, window }) {
17+
const input = target.querySelector('input');
18+
19+
const event = new window.Event('input');
20+
input.value = 'h';
21+
await input.dispatchEvent(event);
22+
23+
assert.equal(input.value, 'H');
24+
assert.htmlEqual(target.innerHTML, `
25+
<input>
26+
<p>H</p>
27+
`);
28+
29+
input.value = 'he';
30+
await input.dispatchEvent(event);
31+
assert.equal(input.value, 'HE');
32+
assert.htmlEqual(target.innerHTML, `
33+
<input>
34+
<p>HE</p>
35+
`);
36+
},
37+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script>
2+
export let value;
3+
4+
function uppercase(event) {
5+
event.target.value = event.target.value.toUpperCase()
6+
}
7+
</script>
8+
9+
<input on:input={uppercase} bind:value>
10+
<p>{value}</p>

0 commit comments

Comments
 (0)