fix: improve bind:this support for each blocks (#10510)

pull/10509/head
Dominic Gannaway 10 months ago committed by GitHub
parent 1700e47858
commit df10204fcc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: improve bind:this support for each blocks

@ -972,7 +972,11 @@ function serialize_inline_component(node, component_name, context) {
const assignment = b.assignment('=', bind_this, b.id('$$value')); const assignment = b.assignment('=', bind_this, b.id('$$value'));
const bind_this_id = /** @type {import('estree').Expression} */ ( const bind_this_id = /** @type {import('estree').Expression} */ (
// if expression is not an identifier, we know it can't be a signal // if expression is not an identifier, we know it can't be a signal
bind_this.type === 'Identifier' ? bind_this : undefined bind_this.type === 'Identifier'
? bind_this
: bind_this.type === 'MemberExpression' && bind_this.object.type === 'Identifier'
? bind_this.object
: undefined
); );
fn = (node_id) => fn = (node_id) =>
b.call( b.call(
@ -2742,7 +2746,11 @@ export const template_visitors = {
setter, setter,
/** @type {import('estree').Expression} */ ( /** @type {import('estree').Expression} */ (
// if expression is not an identifier, we know it can't be a signal // if expression is not an identifier, we know it can't be a signal
expression.type === 'Identifier' ? expression : undefined expression.type === 'Identifier'
? expression
: expression.type === 'MemberExpression' && expression.object.type === 'Identifier'
? expression.object
: undefined
) )
); );
break; break;

@ -69,7 +69,7 @@ import {
} from './utils.js'; } from './utils.js';
import { is_promise } from '../common.js'; import { is_promise } from '../common.js';
import { bind_transition, trigger_transitions } from './transitions.js'; import { bind_transition, trigger_transitions } from './transitions.js';
import { proxy } from './proxy.js'; import { STATE_SYMBOL, proxy } from './proxy.js';
/** @type {Set<string>} */ /** @type {Set<string>} */
const all_registerd_events = new Set(); const all_registerd_events = new Set();
@ -1295,6 +1295,13 @@ export function bind_prop(props, prop, value) {
} }
} }
/**
* @param {unknown} value
*/
function is_state_object(value) {
return value != null && typeof value === 'object' && STATE_SYMBOL in value;
}
/** /**
* @param {Element} element_or_component * @param {Element} element_or_component
* @param {(value: unknown) => void} update * @param {(value: unknown) => void} update
@ -1302,9 +1309,15 @@ export function bind_prop(props, prop, value) {
* @returns {void} * @returns {void}
*/ */
export function bind_this(element_or_component, update, binding) { export function bind_this(element_or_component, update, binding) {
untrack(() => { render_effect(() => {
update(element_or_component); // If we are reading from a proxied state binding, then we don't need to untrack
render_effect(() => () => { // the update function as it will be fine-grain.
if (is_state_object(binding) || (is_signal(binding) && is_state_object(binding.v))) {
update(element_or_component);
} else {
untrack(() => update(element_or_component));
}
return () => {
// Defer to the next tick so that all updates can be reconciled first. // Defer to the next tick so that all updates can be reconciled first.
// This solves the case where one variable is shared across multiple this-bindings. // This solves the case where one variable is shared across multiple this-bindings.
render_effect(() => { render_effect(() => {
@ -1314,7 +1327,7 @@ export function bind_this(element_or_component, update, binding) {
} }
}); });
}); });
}); };
}); });
} }

@ -0,0 +1,13 @@
<script>
const { text } = $props();
let boundParagraph = $state();
export function changeBackgroundToRed() {
boundParagraph.style.backgroundColor = 'red';
}
</script>
<p bind:this={boundParagraph}>
{text}
</p>

@ -0,0 +1,35 @@
import { flushSync } from '../../../../src/main/main-client';
import { test } from '../../test';
export default test({
async test({ assert, target }) {
const [btn, btn2, btn3] = target.querySelectorAll('button');
flushSync(() => {
btn?.click();
});
assert.htmlEqual(
target.innerHTML,
`<div><p style="background-color: red;">b1</p><button>change</button><button>delete</button></div><div><p>b2</p><button>change</button><button>delete</button></div>`
);
flushSync(() => {
btn2?.click();
});
assert.htmlEqual(
target.innerHTML,
`<div><p>b2</p><button>change</button><button>delete</button></div>`
);
flushSync(() => {
btn3?.click();
});
assert.htmlEqual(
target.innerHTML,
`<div><p style="background-color: red;">b2</p><button>change</button><button>delete</button></div>`
);
}
});

@ -0,0 +1,21 @@
<script>
import Paragraph from './Paragraph.svelte';
let boundParagraphs = $state([]);
let store = $state([
{ id: 1, text: 'b1' },
{ id: 2, text: 'b2' }
]);
</script>
{#each store as text, i (text.id)}
<div>
<Paragraph bind:this={boundParagraphs[i]} text={text.text}></Paragraph>
<button onclick={() => boundParagraphs[i].changeBackgroundToRed()}>
change
</button>
<button onclick={() => store.splice(store.indexOf(text), 1)}>
delete
</button>
</div>
{/each}
Loading…
Cancel
Save