feat: omit unnecessary setters from bindings (#13269)

* remove unnecessary closure

* lint

* reuse logic

* feat: omit unnecessary setters in bindings

* changeset

---------

Co-authored-by: adiguba <frederic.martini@gmail.com>
pull/13275/head
Rich Harris 4 days ago committed by GitHub
parent c1d8eb375d
commit 836bc605f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: unwrap function expressions where possible, and optimise bindings

@ -36,13 +36,20 @@ export function BindDirective(node, context) {
); );
} }
const getter = b.thunk(/** @type {Expression} */ (context.visit(expression))); const get = b.thunk(/** @type {Expression} */ (context.visit(expression)));
const setter = b.arrow( /** @type {Expression | undefined} */
[b.id('$$value')], let set = b.unthunk(
/** @type {Expression} */ (context.visit(b.assignment('=', expression, b.id('$$value')))) b.arrow(
[b.id('$$value')],
/** @type {Expression} */ (context.visit(b.assignment('=', expression, b.id('$$value'))))
)
); );
if (get === set) {
set = undefined;
}
/** @type {CallExpression} */ /** @type {CallExpression} */
let call; let call;
@ -52,15 +59,15 @@ export function BindDirective(node, context) {
b.literal(node.name), b.literal(node.name),
b.literal(property.event), b.literal(property.event),
context.state.node, context.state.node,
setter, set ?? get,
property.bidirectional && getter property.bidirectional && get
); );
} else { } else {
// special cases // special cases
switch (node.name) { switch (node.name) {
// window // window
case 'online': case 'online':
call = b.call(`$.bind_online`, setter); call = b.call(`$.bind_online`, set ?? get);
break; break;
case 'scrollX': case 'scrollX':
@ -68,8 +75,8 @@ export function BindDirective(node, context) {
call = b.call( call = b.call(
'$.bind_window_scroll', '$.bind_window_scroll',
b.literal(node.name === 'scrollX' ? 'x' : 'y'), b.literal(node.name === 'scrollX' ? 'x' : 'y'),
getter, get,
setter set
); );
break; break;
@ -77,47 +84,47 @@ export function BindDirective(node, context) {
case 'innerHeight': case 'innerHeight':
case 'outerWidth': case 'outerWidth':
case 'outerHeight': case 'outerHeight':
call = b.call('$.bind_window_size', b.literal(node.name), setter); call = b.call('$.bind_window_size', b.literal(node.name), set ?? get);
break; break;
// document // document
case 'activeElement': case 'activeElement':
call = b.call('$.bind_active_element', setter); call = b.call('$.bind_active_element', set ?? get);
break; break;
// media // media
case 'muted': case 'muted':
call = b.call(`$.bind_muted`, context.state.node, getter, setter); call = b.call(`$.bind_muted`, context.state.node, get, set);
break; break;
case 'paused': case 'paused':
call = b.call(`$.bind_paused`, context.state.node, getter, setter); call = b.call(`$.bind_paused`, context.state.node, get, set);
break; break;
case 'volume': case 'volume':
call = b.call(`$.bind_volume`, context.state.node, getter, setter); call = b.call(`$.bind_volume`, context.state.node, get, set);
break; break;
case 'playbackRate': case 'playbackRate':
call = b.call(`$.bind_playback_rate`, context.state.node, getter, setter); call = b.call(`$.bind_playback_rate`, context.state.node, get, set);
break; break;
case 'currentTime': case 'currentTime':
call = b.call(`$.bind_current_time`, context.state.node, getter, setter); call = b.call(`$.bind_current_time`, context.state.node, get, set);
break; break;
case 'buffered': case 'buffered':
call = b.call(`$.bind_buffered`, context.state.node, setter); call = b.call(`$.bind_buffered`, context.state.node, set ?? get);
break; break;
case 'played': case 'played':
call = b.call(`$.bind_played`, context.state.node, setter); call = b.call(`$.bind_played`, context.state.node, set ?? get);
break; break;
case 'seekable': case 'seekable':
call = b.call(`$.bind_seekable`, context.state.node, setter); call = b.call(`$.bind_seekable`, context.state.node, set ?? get);
break; break;
case 'seeking': case 'seeking':
call = b.call(`$.bind_seeking`, context.state.node, setter); call = b.call(`$.bind_seeking`, context.state.node, set ?? get);
break; break;
case 'ended': case 'ended':
call = b.call(`$.bind_ended`, context.state.node, setter); call = b.call(`$.bind_ended`, context.state.node, set ?? get);
break; break;
case 'readyState': case 'readyState':
call = b.call(`$.bind_ready_state`, context.state.node, setter); call = b.call(`$.bind_ready_state`, context.state.node, set ?? get);
break; break;
// dimensions // dimensions
@ -125,28 +132,33 @@ export function BindDirective(node, context) {
case 'contentBoxSize': case 'contentBoxSize':
case 'borderBoxSize': case 'borderBoxSize':
case 'devicePixelContentBoxSize': case 'devicePixelContentBoxSize':
call = b.call('$.bind_resize_observer', context.state.node, b.literal(node.name), setter); call = b.call(
'$.bind_resize_observer',
context.state.node,
b.literal(node.name),
set ?? get
);
break; break;
case 'clientWidth': case 'clientWidth':
case 'clientHeight': case 'clientHeight':
case 'offsetWidth': case 'offsetWidth':
case 'offsetHeight': case 'offsetHeight':
call = b.call('$.bind_element_size', context.state.node, b.literal(node.name), setter); call = b.call('$.bind_element_size', context.state.node, b.literal(node.name), set ?? get);
break; break;
// various // various
case 'value': { case 'value': {
if (parent?.type === 'RegularElement' && parent.name === 'select') { if (parent?.type === 'RegularElement' && parent.name === 'select') {
call = b.call(`$.bind_select_value`, context.state.node, getter, setter); call = b.call(`$.bind_select_value`, context.state.node, get, set);
} else { } else {
call = b.call(`$.bind_value`, context.state.node, getter, setter); call = b.call(`$.bind_value`, context.state.node, get, set);
} }
break; break;
} }
case 'files': case 'files':
call = b.call(`$.bind_files`, context.state.node, getter, setter); call = b.call(`$.bind_files`, context.state.node, get, set);
break; break;
case 'this': case 'this':
@ -160,18 +172,18 @@ export function BindDirective(node, context) {
'$.bind_content_editable', '$.bind_content_editable',
b.literal(node.name), b.literal(node.name),
context.state.node, context.state.node,
getter, get,
setter set
); );
break; break;
// checkbox/radio // checkbox/radio
case 'checked': case 'checked':
call = b.call(`$.bind_checked`, context.state.node, getter, setter); call = b.call(`$.bind_checked`, context.state.node, get, set);
break; break;
case 'focused': case 'focused':
call = b.call(`$.bind_focused`, context.state.node, setter); call = b.call(`$.bind_focused`, context.state.node, set ?? get);
break; break;
case 'group': { case 'group': {
@ -184,7 +196,7 @@ export function BindDirective(node, context) {
// We need to additionally invoke the value attribute signal to register it as a dependency, // We need to additionally invoke the value attribute signal to register it as a dependency,
// so that when the value is updated, the group binding is updated // so that when the value is updated, the group binding is updated
let group_getter = getter; let group_getter = get;
if (parent?.type === 'RegularElement') { if (parent?.type === 'RegularElement') {
const value = /** @type {any[]} */ ( const value = /** @type {any[]} */ (
@ -215,7 +227,7 @@ export function BindDirective(node, context) {
b.array(indexes), b.array(indexes),
context.state.node, context.state.node,
group_getter, group_getter,
setter set ?? get
); );
break; break;
} }

@ -419,19 +419,31 @@ export function template(elements, expressions) {
* @returns {ESTree.Expression} * @returns {ESTree.Expression}
*/ */
export function thunk(expression, async = false) { export function thunk(expression, async = false) {
const fn = arrow([], expression);
if (async) fn.async = true;
return unthunk(fn);
}
/**
* Replace "(arg) => func(arg)" to "func"
* @param {ESTree.Expression} expression
* @returns {ESTree.Expression}
*/
export function unthunk(expression) {
if ( if (
expression.type === 'CallExpression' && expression.type === 'ArrowFunctionExpression' &&
expression.callee.type !== 'Super' && expression.async === false &&
expression.callee.type !== 'MemberExpression' && expression.body.type === 'CallExpression' &&
expression.callee.type !== 'CallExpression' && expression.body.callee.type === 'Identifier' &&
expression.arguments.length === 0 expression.params.length === expression.body.arguments.length &&
expression.params.every((param, index) => {
const arg = /** @type {ESTree.SimpleCallExpression} */ (expression.body).arguments[index];
return param.type === 'Identifier' && arg.type === 'Identifier' && param.name === arg.name;
})
) { ) {
return expression.callee; return expression.body.callee;
} }
return expression;
const fn = arrow([], expression);
if (async) fn.async = true;
return fn;
} }
/** /**

@ -8,18 +8,18 @@ import { hydrating } from '../../hydration.js';
/** /**
* @param {HTMLInputElement} input * @param {HTMLInputElement} input
* @param {() => unknown} get_value * @param {() => unknown} get
* @param {(value: unknown) => void} update * @param {(value: unknown) => void} set
* @returns {void} * @returns {void}
*/ */
export function bind_value(input, get_value, update) { export function bind_value(input, get, set = get) {
listen_to_event_and_reset_event(input, 'input', () => { listen_to_event_and_reset_event(input, 'input', () => {
if (DEV && input.type === 'checkbox') { if (DEV && input.type === 'checkbox') {
// TODO should this happen in prod too? // TODO should this happen in prod too?
e.bind_invalid_checkbox_value(); e.bind_invalid_checkbox_value();
} }
update(is_numberlike_input(input) ? to_number(input.value) : input.value); set(is_numberlike_input(input) ? to_number(input.value) : input.value);
}); });
render_effect(() => { render_effect(() => {
@ -28,12 +28,12 @@ export function bind_value(input, get_value, update) {
e.bind_invalid_checkbox_value(); e.bind_invalid_checkbox_value();
} }
var value = get_value(); var value = get();
// If we are hydrating and the value has since changed, then use the update value // If we are hydrating and the value has since changed, then use the update value
// from the input instead. // from the input instead.
if (hydrating && input.defaultValue !== input.value) { if (hydrating && input.defaultValue !== input.value) {
update(input.value); set(input.value);
return; return;
} }
@ -60,11 +60,11 @@ const pending = new Set();
* @param {HTMLInputElement[]} inputs * @param {HTMLInputElement[]} inputs
* @param {null | [number]} group_index * @param {null | [number]} group_index
* @param {HTMLInputElement} input * @param {HTMLInputElement} input
* @param {() => unknown} get_value * @param {() => unknown} get
* @param {(value: unknown) => void} update * @param {(value: unknown) => void} set
* @returns {void} * @returns {void}
*/ */
export function bind_group(inputs, group_index, input, get_value, update) { export function bind_group(inputs, group_index, input, get, set = get) {
var is_checkbox = input.getAttribute('type') === 'checkbox'; var is_checkbox = input.getAttribute('type') === 'checkbox';
var binding_group = inputs; var binding_group = inputs;
@ -91,14 +91,14 @@ export function bind_group(inputs, group_index, input, get_value, update) {
value = get_binding_group_value(binding_group, value, input.checked); value = get_binding_group_value(binding_group, value, input.checked);
} }
update(value); set(value);
}, },
// TODO better default value handling // TODO better default value handling
() => update(is_checkbox ? [] : null) () => set(is_checkbox ? [] : null)
); );
render_effect(() => { render_effect(() => {
var value = get_value(); var value = get();
// If we are hydrating and the value has since changed, then use the update value // If we are hydrating and the value has since changed, then use the update value
// from the input instead. // from the input instead.
@ -147,29 +147,29 @@ export function bind_group(inputs, group_index, input, get_value, update) {
value = hydration_input?.__value; value = hydration_input?.__value;
} }
update(value); set(value);
} }
}); });
} }
/** /**
* @param {HTMLInputElement} input * @param {HTMLInputElement} input
* @param {() => unknown} get_value * @param {() => unknown} get
* @param {(value: unknown) => void} update * @param {(value: unknown) => void} set
* @returns {void} * @returns {void}
*/ */
export function bind_checked(input, get_value, update) { export function bind_checked(input, get, set = get) {
listen_to_event_and_reset_event(input, 'change', () => { listen_to_event_and_reset_event(input, 'change', () => {
var value = input.checked; var value = input.checked;
update(value); set(value);
}); });
if (get_value() == undefined) { if (get() == undefined) {
update(false); set(false);
} }
render_effect(() => { render_effect(() => {
var value = get_value(); var value = get();
input.checked = Boolean(value); input.checked = Boolean(value);
}); });
} }
@ -215,15 +215,15 @@ function to_number(value) {
/** /**
* @param {HTMLInputElement} input * @param {HTMLInputElement} input
* @param {() => FileList | null} get_value * @param {() => FileList | null} get
* @param {(value: FileList | null) => void} update * @param {(value: FileList | null) => void} set
*/ */
export function bind_files(input, get_value, update) { export function bind_files(input, get, set = get) {
listen_to_event_and_reset_event(input, 'change', () => { listen_to_event_and_reset_event(input, 'change', () => {
update(input.files); set(input.files);
}); });
render_effect(() => { render_effect(() => {
input.files = get_value(); input.files = get();
}); });
} }

@ -15,11 +15,11 @@ function time_ranges_to_array(ranges) {
/** /**
* @param {HTMLVideoElement | HTMLAudioElement} media * @param {HTMLVideoElement | HTMLAudioElement} media
* @param {() => number | undefined} get_value * @param {() => number | undefined} get
* @param {(value: number) => void} update * @param {(value: number) => void} set
* @returns {void} * @returns {void}
*/ */
export function bind_current_time(media, get_value, update) { export function bind_current_time(media, get, set = get) {
/** @type {number} */ /** @type {number} */
var raf_id; var raf_id;
/** @type {number} */ /** @type {number} */
@ -37,7 +37,7 @@ export function bind_current_time(media, get_value, update) {
var next_value = media.currentTime; var next_value = media.currentTime;
if (value !== next_value) { if (value !== next_value) {
update((value = next_value)); set((value = next_value));
} }
}; };
@ -45,7 +45,7 @@ export function bind_current_time(media, get_value, update) {
media.addEventListener('timeupdate', callback); media.addEventListener('timeupdate', callback);
render_effect(() => { render_effect(() => {
var next_value = Number(get_value()); var next_value = Number(get());
if (value !== next_value && !isNaN(/** @type {any} */ (next_value))) { if (value !== next_value && !isNaN(/** @type {any} */ (next_value))) {
media.currentTime = value = next_value; media.currentTime = value = next_value;
@ -57,66 +57,66 @@ export function bind_current_time(media, get_value, update) {
/** /**
* @param {HTMLVideoElement | HTMLAudioElement} media * @param {HTMLVideoElement | HTMLAudioElement} media
* @param {(array: Array<{ start: number; end: number }>) => void} update * @param {(array: Array<{ start: number; end: number }>) => void} set
*/ */
export function bind_buffered(media, update) { export function bind_buffered(media, set) {
listen(media, ['loadedmetadata', 'progress'], () => update(time_ranges_to_array(media.buffered))); listen(media, ['loadedmetadata', 'progress'], () => set(time_ranges_to_array(media.buffered)));
} }
/** /**
* @param {HTMLVideoElement | HTMLAudioElement} media * @param {HTMLVideoElement | HTMLAudioElement} media
* @param {(array: Array<{ start: number; end: number }>) => void} update * @param {(array: Array<{ start: number; end: number }>) => void} set
*/ */
export function bind_seekable(media, update) { export function bind_seekable(media, set) {
listen(media, ['loadedmetadata'], () => update(time_ranges_to_array(media.seekable))); listen(media, ['loadedmetadata'], () => set(time_ranges_to_array(media.seekable)));
} }
/** /**
* @param {HTMLVideoElement | HTMLAudioElement} media * @param {HTMLVideoElement | HTMLAudioElement} media
* @param {(array: Array<{ start: number; end: number }>) => void} update * @param {(array: Array<{ start: number; end: number }>) => void} set
*/ */
export function bind_played(media, update) { export function bind_played(media, set) {
listen(media, ['timeupdate'], () => update(time_ranges_to_array(media.played))); listen(media, ['timeupdate'], () => set(time_ranges_to_array(media.played)));
} }
/** /**
* @param {HTMLVideoElement | HTMLAudioElement} media * @param {HTMLVideoElement | HTMLAudioElement} media
* @param {(seeking: boolean) => void} update * @param {(seeking: boolean) => void} set
*/ */
export function bind_seeking(media, update) { export function bind_seeking(media, set) {
listen(media, ['seeking', 'seeked'], () => update(media.seeking)); listen(media, ['seeking', 'seeked'], () => set(media.seeking));
} }
/** /**
* @param {HTMLVideoElement | HTMLAudioElement} media * @param {HTMLVideoElement | HTMLAudioElement} media
* @param {(seeking: boolean) => void} update * @param {(seeking: boolean) => void} set
*/ */
export function bind_ended(media, update) { export function bind_ended(media, set) {
listen(media, ['timeupdate', 'ended'], () => update(media.ended)); listen(media, ['timeupdate', 'ended'], () => set(media.ended));
} }
/** /**
* @param {HTMLVideoElement | HTMLAudioElement} media * @param {HTMLVideoElement | HTMLAudioElement} media
* @param {(ready_state: number) => void} update * @param {(ready_state: number) => void} set
*/ */
export function bind_ready_state(media, update) { export function bind_ready_state(media, set) {
listen( listen(
media, media,
['loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'playing', 'waiting', 'emptied'], ['loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'playing', 'waiting', 'emptied'],
() => update(media.readyState) () => set(media.readyState)
); );
} }
/** /**
* @param {HTMLVideoElement | HTMLAudioElement} media * @param {HTMLVideoElement | HTMLAudioElement} media
* @param {() => number | undefined} get_value * @param {() => number | undefined} get
* @param {(playback_rate: number) => void} update * @param {(playback_rate: number) => void} set
*/ */
export function bind_playback_rate(media, get_value, update) { export function bind_playback_rate(media, get, set = get) {
// Needs to happen after element is inserted into the dom (which is guaranteed by using effect), // Needs to happen after element is inserted into the dom (which is guaranteed by using effect),
// else playback will be set back to 1 by the browser // else playback will be set back to 1 by the browser
effect(() => { effect(() => {
var value = Number(get_value()); var value = Number(get());
if (value !== media.playbackRate && !isNaN(value)) { if (value !== media.playbackRate && !isNaN(value)) {
media.playbackRate = value; media.playbackRate = value;
@ -127,24 +127,24 @@ export function bind_playback_rate(media, get_value, update) {
// else playback will be set to 1 by the browser // else playback will be set to 1 by the browser
effect(() => { effect(() => {
listen(media, ['ratechange'], () => { listen(media, ['ratechange'], () => {
update(media.playbackRate); set(media.playbackRate);
}); });
}); });
} }
/** /**
* @param {HTMLVideoElement | HTMLAudioElement} media * @param {HTMLVideoElement | HTMLAudioElement} media
* @param {() => boolean | undefined} get_value * @param {() => boolean | undefined} get
* @param {(paused: boolean) => void} update * @param {(paused: boolean) => void} set
*/ */
export function bind_paused(media, get_value, update) { export function bind_paused(media, get, set = get) {
var mounted = hydrating; var mounted = hydrating;
var paused = get_value(); var paused = get();
var callback = () => { var callback = () => {
if (paused !== media.paused) { if (paused !== media.paused) {
paused = media.paused; paused = media.paused;
update((paused = media.paused)); set((paused = media.paused));
} }
}; };
@ -160,7 +160,7 @@ export function bind_paused(media, get_value, update) {
} }
render_effect(() => { render_effect(() => {
paused = !!get_value(); paused = !!get();
if (paused !== media.paused) { if (paused !== media.paused) {
var toggle = () => { var toggle = () => {
@ -169,7 +169,7 @@ export function bind_paused(media, get_value, update) {
media.pause(); media.pause();
} else { } else {
media.play().catch(() => { media.play().catch(() => {
update((paused = true)); set((paused = true));
}); });
} }
}; };
@ -195,22 +195,22 @@ export function bind_paused(media, get_value, update) {
/** /**
* @param {HTMLVideoElement | HTMLAudioElement} media * @param {HTMLVideoElement | HTMLAudioElement} media
* @param {() => number | undefined} get_value * @param {() => number | undefined} get
* @param {(volume: number) => void} update * @param {(volume: number) => void} set
*/ */
export function bind_volume(media, get_value, update) { export function bind_volume(media, get, set = get) {
var callback = () => { var callback = () => {
update(media.volume); set(media.volume);
}; };
if (get_value() == null) { if (get() == null) {
callback(); callback();
} }
listen(media, ['volumechange'], callback, false); listen(media, ['volumechange'], callback, false);
render_effect(() => { render_effect(() => {
var value = Number(get_value()); var value = Number(get());
if (value !== media.volume && !isNaN(value)) { if (value !== media.volume && !isNaN(value)) {
media.volume = value; media.volume = value;
@ -220,22 +220,22 @@ export function bind_volume(media, get_value, update) {
/** /**
* @param {HTMLVideoElement | HTMLAudioElement} media * @param {HTMLVideoElement | HTMLAudioElement} media
* @param {() => boolean | undefined} get_value * @param {() => boolean | undefined} get
* @param {(muted: boolean) => void} update * @param {(muted: boolean) => void} set
*/ */
export function bind_muted(media, get_value, update) { export function bind_muted(media, get, set = get) {
var callback = () => { var callback = () => {
update(media.muted); set(media.muted);
}; };
if (get_value() == null) { if (get() == null) {
callback(); callback();
} }
listen(media, ['volumechange'], callback, false); listen(media, ['volumechange'], callback, false);
render_effect(() => { render_effect(() => {
var value = !!get_value(); var value = !!get();
if (media.muted !== value) media.muted = value; if (media.muted !== value) media.muted = value;
}); });

@ -73,11 +73,11 @@ export function init_select(select, get_value) {
/** /**
* @param {HTMLSelectElement} select * @param {HTMLSelectElement} select
* @param {() => unknown} get_value * @param {() => unknown} get
* @param {(value: unknown) => void} update * @param {(value: unknown) => void} set
* @returns {void} * @returns {void}
*/ */
export function bind_select_value(select, get_value, update) { export function bind_select_value(select, get, set = get) {
var mounting = true; var mounting = true;
listen_to_event_and_reset_event(select, 'change', () => { listen_to_event_and_reset_event(select, 'change', () => {
@ -92,12 +92,12 @@ export function bind_select_value(select, get_value, update) {
value = selected_option && get_option_value(selected_option); value = selected_option && get_option_value(selected_option);
} }
update(value); set(value);
}); });
// 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 // 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
effect(() => { effect(() => {
var value = get_value(); var value = get();
select_option(select, value, mounting); select_option(select, value, mounting);
// Mounting and value undefined -> take selection from dom // Mounting and value undefined -> take selection from dom
@ -106,7 +106,7 @@ export function bind_select_value(select, get_value, update) {
var selected_option = select.querySelector(':checked'); var selected_option = select.querySelector(':checked');
if (selected_option !== null) { if (selected_option !== null) {
value = get_option_value(selected_option); value = get_option_value(selected_option);
update(value); set(value);
} }
} }

@ -78,9 +78,9 @@ var resize_observer_device_pixel_content_box = /* @__PURE__ */ new ResizeObserve
/** /**
* @param {Element} element * @param {Element} element
* @param {'contentRect' | 'contentBoxSize' | 'borderBoxSize' | 'devicePixelContentBoxSize'} type * @param {'contentRect' | 'contentBoxSize' | 'borderBoxSize' | 'devicePixelContentBoxSize'} type
* @param {(entry: keyof ResizeObserverEntry) => void} update * @param {(entry: keyof ResizeObserverEntry) => void} set
*/ */
export function bind_resize_observer(element, type, update) { export function bind_resize_observer(element, type, set) {
var observer = var observer =
type === 'contentRect' || type === 'contentBoxSize' type === 'contentRect' || type === 'contentBoxSize'
? resize_observer_content_box ? resize_observer_content_box
@ -88,21 +88,21 @@ export function bind_resize_observer(element, type, update) {
? resize_observer_border_box ? resize_observer_border_box
: resize_observer_device_pixel_content_box; : resize_observer_device_pixel_content_box;
var unsub = observer.observe(element, /** @param {any} entry */ (entry) => update(entry[type])); var unsub = observer.observe(element, /** @param {any} entry */ (entry) => set(entry[type]));
teardown(unsub); teardown(unsub);
} }
/** /**
* @param {HTMLElement} element * @param {HTMLElement} element
* @param {'clientWidth' | 'clientHeight' | 'offsetWidth' | 'offsetHeight'} type * @param {'clientWidth' | 'clientHeight' | 'offsetWidth' | 'offsetHeight'} type
* @param {(size: number) => void} update * @param {(size: number) => void} set
*/ */
export function bind_element_size(element, type, update) { export function bind_element_size(element, type, set) {
var unsub = resize_observer_border_box.observe(element, () => update(element[type])); var unsub = resize_observer_border_box.observe(element, () => set(element[type]));
effect(() => { effect(() => {
// The update could contain reads which should be ignored // The update could contain reads which should be ignored
untrack(() => update(element[type])); untrack(() => set(element[type]));
return unsub; return unsub;
}); });
} }

@ -4,24 +4,24 @@ import { listen } from './shared.js';
/** /**
* @param {'innerHTML' | 'textContent' | 'innerText'} property * @param {'innerHTML' | 'textContent' | 'innerText'} property
* @param {HTMLElement} element * @param {HTMLElement} element
* @param {() => unknown} get_value * @param {() => unknown} get
* @param {(value: unknown) => void} update * @param {(value: unknown) => void} set
* @returns {void} * @returns {void}
*/ */
export function bind_content_editable(property, element, get_value, update) { export function bind_content_editable(property, element, get, set = get) {
element.addEventListener('input', () => { element.addEventListener('input', () => {
// @ts-ignore // @ts-ignore
update(element[property]); set(element[property]);
}); });
render_effect(() => { render_effect(() => {
var value = get_value(); var value = get();
if (element[property] !== value) { if (element[property] !== value) {
if (value == null) { if (value == null) {
// @ts-ignore // @ts-ignore
var non_null_value = element[property]; var non_null_value = element[property];
update(non_null_value); set(non_null_value);
} else { } else {
// @ts-ignore // @ts-ignore
element[property] = value + ''; element[property] = value + '';
@ -65,11 +65,11 @@ export function bind_property(property, event_name, element, set, get) {
/** /**
* @param {HTMLElement} element * @param {HTMLElement} element
* @param {(value: unknown) => void} update * @param {(value: unknown) => void} set
* @returns {void} * @returns {void}
*/ */
export function bind_focused(element, update) { export function bind_focused(element, set) {
listen(element, ['focus', 'blur'], () => { listen(element, ['focus', 'blur'], () => {
update(element === document.activeElement); set(element === document.activeElement);
}); });
} }

@ -3,11 +3,11 @@ import { listen } from './shared.js';
/** /**
* @param {'x' | 'y'} type * @param {'x' | 'y'} type
* @param {() => number} get_value * @param {() => number} get
* @param {(value: number) => void} update * @param {(value: number) => void} set
* @returns {void} * @returns {void}
*/ */
export function bind_window_scroll(type, get_value, update) { export function bind_window_scroll(type, get, set = get) {
var is_scrolling_x = type === 'x'; var is_scrolling_x = type === 'x';
var target_handler = () => { var target_handler = () => {
@ -15,7 +15,7 @@ export function bind_window_scroll(type, get_value, update) {
clearTimeout(timeout); clearTimeout(timeout);
timeout = setTimeout(clear, 100); // TODO use scrollend event if supported (or when supported everywhere?) timeout = setTimeout(clear, 100); // TODO use scrollend event if supported (or when supported everywhere?)
update(window[is_scrolling_x ? 'scrollX' : 'scrollY']); set(window[is_scrolling_x ? 'scrollX' : 'scrollY']);
}; };
addEventListener('scroll', target_handler, { addEventListener('scroll', target_handler, {
@ -32,7 +32,7 @@ export function bind_window_scroll(type, get_value, update) {
var first = true; var first = true;
render_effect(() => { render_effect(() => {
var latest_value = get_value(); var latest_value = get();
// Don't scroll to the initial value for accessibility reasons // Don't scroll to the initial value for accessibility reasons
if (first) { if (first) {
first = false; first = false;
@ -58,8 +58,8 @@ export function bind_window_scroll(type, get_value, update) {
/** /**
* @param {'innerWidth' | 'innerHeight' | 'outerWidth' | 'outerHeight'} type * @param {'innerWidth' | 'innerHeight' | 'outerWidth' | 'outerHeight'} type
* @param {(size: number) => void} update * @param {(size: number) => void} set
*/ */
export function bind_window_size(type, update) { export function bind_window_size(type, set) {
listen(window, ['resize'], () => update(window[type])); listen(window, ['resize'], () => set(window[type]));
} }

Loading…
Cancel
Save