Skip to content

Commit fe51cde

Browse files
authored
breaking: event handlers + bindings now yield effect updates (#11706)
* breaking: delegated event handlers now yield effect updates * tweak * refactor * refactor * yield binding change events * handle input event bindings * more bindings * more bindings * more tests * more tests * address feedback * address feedback
1 parent 3498df8 commit fe51cde

File tree

12 files changed

+97
-38
lines changed

12 files changed

+97
-38
lines changed

.changeset/popular-cameras-tie.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
breaking: event handlers + bindings now yield effect updates

packages/svelte/src/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export const RawTextElements = ['textarea', 'script', 'style', 'title'];
3636
export const DelegatedEvents = [
3737
'beforeinput',
3838
'click',
39+
'change',
3940
'dblclick',
4041
'contextmenu',
4142
'focusin',

packages/svelte/src/internal/client/dom/elements/bindings/input.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { stringify } from '../../../render.js';
44
import { listen_to_event_and_reset_event } from './shared.js';
55
import * as e from '../../../errors.js';
66
import { get_proxied_value, is } from '../../../proxy.js';
7+
import { yield_updates } from '../../../runtime.js';
78

89
/**
910
* @param {HTMLInputElement} input
@@ -18,7 +19,7 @@ export function bind_value(input, get_value, update) {
1819
e.bind_invalid_checkbox_value();
1920
}
2021

21-
update(is_numberlike_input(input) ? to_number(input.value) : input.value);
22+
yield_updates(() => update(is_numberlike_input(input) ? to_number(input.value) : input.value));
2223
});
2324

2425
render_effect(() => {
@@ -84,10 +85,10 @@ export function bind_group(inputs, group_index, input, get_value, update) {
8485
value = get_binding_group_value(binding_group, value, input.checked);
8586
}
8687

87-
update(value);
88+
yield_updates(() => update(value));
8889
},
8990
// TODO better default value handling
90-
() => update(is_checkbox ? [] : null)
91+
() => yield_updates(() => update(is_checkbox ? [] : null))
9192
);
9293

9394
render_effect(() => {
@@ -128,7 +129,7 @@ export function bind_group(inputs, group_index, input, get_value, update) {
128129
export function bind_checked(input, get_value, update) {
129130
listen_to_event_and_reset_event(input, 'change', () => {
130131
var value = input.checked;
131-
update(value);
132+
yield_updates(() => update(value));
132133
});
133134

134135
if (get_value() == undefined) {
@@ -187,7 +188,7 @@ function to_number(value) {
187188
*/
188189
export function bind_files(input, get_value, update) {
189190
listen_to_event_and_reset_event(input, 'change', () => {
190-
update(input.files);
191+
yield_updates(() => update(input.files));
191192
});
192193
render_effect(() => {
193194
input.files = get_value();

packages/svelte/src/internal/client/dom/elements/bindings/media.js

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { hydrating } from '../../hydration.js';
22
import { render_effect, effect } from '../../../reactivity/effects.js';
33
import { listen } from './shared.js';
4+
import { yield_updates } from '../../../runtime.js';
45

56
/** @param {TimeRanges} ranges */
67
function time_ranges_to_array(ranges) {
@@ -35,7 +36,7 @@ export function bind_current_time(media, get_value, update) {
3536
}
3637

3738
updating = true;
38-
update(media.currentTime);
39+
yield_updates(() => update(media.currentTime));
3940
};
4041

4142
raf_id = requestAnimationFrame(callback);
@@ -60,7 +61,9 @@ export function bind_current_time(media, get_value, update) {
6061
* @param {(array: Array<{ start: number; end: number }>) => void} update
6162
*/
6263
export function bind_buffered(media, update) {
63-
listen(media, ['loadedmetadata', 'progress'], () => update(time_ranges_to_array(media.buffered)));
64+
listen(media, ['loadedmetadata', 'progress'], () =>
65+
yield_updates(() => update(time_ranges_to_array(media.buffered)))
66+
);
6467
}
6568

6669
/**
@@ -76,23 +79,25 @@ export function bind_seekable(media, update) {
7679
* @param {(array: Array<{ start: number; end: number }>) => void} update
7780
*/
7881
export function bind_played(media, update) {
79-
listen(media, ['timeupdate'], () => update(time_ranges_to_array(media.played)));
82+
listen(media, ['timeupdate'], () =>
83+
yield_updates(() => update(time_ranges_to_array(media.played)))
84+
);
8085
}
8186

8287
/**
8388
* @param {HTMLVideoElement | HTMLAudioElement} media
8489
* @param {(seeking: boolean) => void} update
8590
*/
8691
export function bind_seeking(media, update) {
87-
listen(media, ['seeking', 'seeked'], () => update(media.seeking));
92+
listen(media, ['seeking', 'seeked'], () => yield_updates(() => update(media.seeking)));
8893
}
8994

9095
/**
9196
* @param {HTMLVideoElement | HTMLAudioElement} media
9297
* @param {(seeking: boolean) => void} update
9398
*/
9499
export function bind_ended(media, update) {
95-
listen(media, ['timeupdate', 'ended'], () => update(media.ended));
100+
listen(media, ['timeupdate', 'ended'], () => yield_updates(() => update(media.ended)));
96101
}
97102

98103
/**
@@ -103,7 +108,7 @@ export function bind_ready_state(media, update) {
103108
listen(
104109
media,
105110
['loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'playing', 'waiting', 'emptied'],
106-
() => update(media.readyState)
111+
() => yield_updates(() => update(media.readyState))
107112
);
108113
}
109114

@@ -127,7 +132,7 @@ export function bind_playback_rate(media, get_value, update) {
127132
}
128133

129134
listen(media, ['ratechange'], () => {
130-
if (!updating) update(media.playbackRate);
135+
if (!updating) yield_updates(() => update(media.playbackRate));
131136
updating = false;
132137
});
133138
});
@@ -145,7 +150,7 @@ export function bind_paused(media, get_value, update) {
145150
var callback = () => {
146151
if (paused !== media.paused) {
147152
paused = media.paused;
148-
update((paused = media.paused));
153+
yield_updates(() => update((paused = media.paused)));
149154
}
150155
};
151156

@@ -170,7 +175,7 @@ export function bind_paused(media, get_value, update) {
170175
media.pause();
171176
} else {
172177
media.play().catch(() => {
173-
update((paused = true));
178+
yield_updates(() => update((paused = true)));
174179
});
175180
}
176181
};
@@ -234,7 +239,7 @@ export function bind_muted(media, get_value, update) {
234239

235240
var callback = () => {
236241
updating = true;
237-
update(media.muted);
242+
yield_updates(() => update(media.muted));
238243
};
239244

240245
if (get_value() == null) {
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { yield_updates } from '../../../runtime.js';
12
import { listen } from './shared.js';
23

34
/**
@@ -6,6 +7,6 @@ import { listen } from './shared.js';
67
*/
78
export function bind_online(update) {
89
listen(window, ['online', 'offline'], () => {
9-
update(navigator.onLine);
10+
yield_updates(() => update(navigator.onLine));
1011
});
1112
}

packages/svelte/src/internal/client/dom/elements/bindings/select.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { effect } from '../../../reactivity/effects.js';
22
import { listen_to_event_and_reset_event } from './shared.js';
3-
import { untrack } from '../../../runtime.js';
3+
import { untrack, yield_updates } from '../../../runtime.js';
44
import { is } from '../../../proxy.js';
55

66
/**
@@ -90,7 +90,7 @@ export function bind_select_value(select, get_value, update) {
9090
value = selected_option && get_option_value(selected_option);
9191
}
9292

93-
update(value);
93+
yield_updates(() => update(value));
9494
});
9595

9696
// Needs to be an effect, not a render_effect, so that in case of each loops the logic runs after the each block has updated

packages/svelte/src/internal/client/dom/elements/bindings/size.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { effect, render_effect } from '../../../reactivity/effects.js';
2-
import { untrack } from '../../../runtime.js';
2+
import { untrack, yield_updates } from '../../../runtime.js';
33

44
/**
55
* Resize observer singleton.
@@ -88,7 +88,10 @@ export function bind_resize_observer(element, type, update) {
8888
? resize_observer_border_box
8989
: resize_observer_device_pixel_content_box;
9090

91-
var unsub = observer.observe(element, /** @param {any} entry */ (entry) => update(entry[type]));
91+
var unsub = observer.observe(
92+
element,
93+
/** @param {any} entry */ (entry) => yield_updates(() => update(entry[type]))
94+
);
9295
render_effect(() => unsub);
9396
}
9497

@@ -101,7 +104,7 @@ export function bind_element_size(element, type, update) {
101104
var unsub = resize_observer_border_box.observe(element, () => update(element[type]));
102105

103106
effect(() => {
104-
untrack(() => update(element[type]));
107+
yield_updates(() => untrack(() => update(element[type])));
105108
return unsub;
106109
});
107110
}

packages/svelte/src/internal/client/dom/elements/bindings/this.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { STATE_SYMBOL } from '../../../constants.js';
22
import { effect, render_effect } from '../../../reactivity/effects.js';
3-
import { untrack } from '../../../runtime.js';
3+
import { untrack, yield_updates } from '../../../runtime.js';
44
import { queue_micro_task } from '../../task.js';
55

66
/**
@@ -37,12 +37,14 @@ export function bind_this(element_or_component, update, get_value, get_parts) {
3737

3838
untrack(() => {
3939
if (element_or_component !== get_value(...parts)) {
40-
update(element_or_component, ...parts);
41-
// If this is an effect rerun (cause: each block context changes), then nullfiy the binding at
42-
// the previous position if it isn't already taken over by a different effect.
43-
if (old_parts && is_bound_this(get_value(...old_parts), element_or_component)) {
44-
update(null, ...old_parts);
45-
}
40+
yield_updates(() => {
41+
update(element_or_component, ...parts);
42+
// If this is an effect rerun (cause: each block context changes), then nullfiy the binding at
43+
// the previous position if it isn't already taken over by a different effect.
44+
if (old_parts && is_bound_this(get_value(...old_parts), element_or_component)) {
45+
update(null, ...old_parts);
46+
}
47+
});
4648
}
4749
});
4850
});

packages/svelte/src/internal/client/dom/elements/bindings/window.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { effect, render_effect } from '../../../reactivity/effects.js';
2+
import { yield_updates } from '../../../runtime.js';
23
import { listen } from './shared.js';
34

45
/**
@@ -15,7 +16,7 @@ export function bind_window_scroll(type, get_value, update) {
1516
clearTimeout(timeout);
1617
timeout = setTimeout(clear, 100); // TODO use scrollend event if supported (or when supported everywhere?)
1718

18-
update(window[is_scrolling_x ? 'scrollX' : 'scrollY']);
19+
yield_updates(() => update(window[is_scrolling_x ? 'scrollX' : 'scrollY']));
1920
};
2021

2122
addEventListener('scroll', target_handler, {
@@ -53,7 +54,7 @@ export function bind_window_scroll(type, get_value, update) {
5354
effect(() => {
5455
var value = window[is_scrolling_x ? 'scrollX' : 'scrollY'];
5556
if (value === 0) {
56-
update(value);
57+
yield_updates(() => update(value));
5758
}
5859
});
5960

@@ -69,5 +70,5 @@ export function bind_window_scroll(type, get_value, update) {
6970
* @param {(size: number) => void} update
7071
*/
7172
export function bind_window_size(type, update) {
72-
listen(window, ['resize'], () => update(window[type]));
73+
listen(window, ['resize'], () => yield_updates(() => update(window[type])));
7374
}

packages/svelte/src/internal/client/dom/elements/events.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { render_effect } from '../../reactivity/effects.js';
22
import { all_registered_events, root_event_handles } from '../../render.js';
3+
import { yield_updates } from '../../runtime.js';
34
import { define_property, is_array } from '../../utils.js';
45
import { hydrating } from '../hydration.js';
56
import { queue_micro_task } from '../task.js';
@@ -47,7 +48,7 @@ export function create_event(event_name, dom, handler, options) {
4748
handle_event_propagation(dom, event);
4849
}
4950
if (!event.cancelBubble) {
50-
return handler.call(this, event);
51+
return yield_updates(() => handler.call(this, event));
5152
}
5253
}
5354

@@ -203,7 +204,7 @@ export function handle_event_propagation(handler_element, event) {
203204
}
204205

205206
try {
206-
next(current_target);
207+
yield_updates(() => next(/** @type {Element} */ (current_target)));
207208
} finally {
208209
// @ts-expect-error is used above
209210
event.__root = handler_element;

0 commit comments

Comments
 (0)