Skip to content

Commit 509f92d

Browse files
trueadmdummdidumm
andauthored
fix: correctly assign bind:this with multiples (#9617)
* fix: correctly assign bind:this with multiples * better fix * better fix * lint * lint * Update packages/svelte/src/internal/client/render.js --------- Co-authored-by: Simon H <[email protected]>
1 parent c22ebff commit 509f92d

File tree

5 files changed

+73
-7
lines changed

5 files changed

+73
-7
lines changed

.changeset/dirty-tips-add.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+
fix: correct bind this multiple bindings

packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -891,14 +891,16 @@ function serialize_inline_component(node, component_name, context) {
891891
if (bind_this !== null) {
892892
const prev = fn;
893893
const assignment = b.assignment('=', bind_this, b.id('$$value'));
894+
const bind_this_id = bind_this;
894895
fn = (node_id) =>
895896
b.call(
896897
'$.bind_this',
897898
prev(node_id),
898899
b.arrow(
899900
[b.id('$$value')],
900901
serialize_set_binding(assignment, context, () => context.visit(assignment))
901-
)
902+
),
903+
bind_this_id
902904
);
903905
}
904906

@@ -2620,7 +2622,7 @@ export const template_visitors = {
26202622
}
26212623

26222624
case 'this':
2623-
call_expr = b.call(`$.bind_this`, state.node, setter);
2625+
call_expr = b.call(`$.bind_this`, state.node, setter, node.expression);
26242626
break;
26252627

26262628
case 'textContent':

packages/svelte/src/internal/client/render.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { DEV } from 'esm-env';
22
import {
33
append_child,
44
child,
5-
child_frag,
65
clone_node,
76
create_element,
87
init_operations,
@@ -61,7 +60,8 @@ import {
6160
push,
6261
current_component_context,
6362
pop,
64-
schedule_task
63+
schedule_task,
64+
managed_render_effect
6565
} from './runtime.js';
6666
import {
6767
current_hydration_fragment,
@@ -1225,14 +1225,21 @@ export function bind_prop(props, prop, value) {
12251225
/**
12261226
* @param {Element} element_or_component
12271227
* @param {(value: unknown) => void} update
1228+
* @param {import('./types.js').MaybeSignal} binding
12281229
* @returns {void}
12291230
*/
1230-
export function bind_this(element_or_component, update) {
1231+
export function bind_this(element_or_component, update, binding) {
12311232
untrack(() => {
12321233
update(element_or_component);
12331234
render_effect(() => () => {
1234-
untrack(() => {
1235-
update(null);
1235+
// Defer to the next tick so that all updates can be reconciled first.
1236+
// This solves the case where one variable is shared across multiple this-bindings.
1237+
render_effect(() => {
1238+
untrack(() => {
1239+
if (!is_signal(binding) || binding.v === element_or_component) {
1240+
update(null);
1241+
}
1242+
});
12361243
});
12371244
});
12381245
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
test({ assert, component, target }) {
6+
const [b1, b2, b3] = target.querySelectorAll('button');
7+
const first_h1 = target.querySelector('h1');
8+
9+
assert.deepEqual(component.log, [undefined, first_h1]);
10+
11+
flushSync(() => {
12+
b3.click();
13+
});
14+
15+
const third_h1 = target.querySelector('h1');
16+
17+
assert.deepEqual(component.log, [undefined, first_h1, third_h1]);
18+
19+
flushSync(() => {
20+
b1.click();
21+
});
22+
23+
assert.deepEqual(component.log, [undefined, first_h1, third_h1, target.querySelector('h1')]);
24+
}
25+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<script>
2+
let activeTab = 0;
3+
let activeHeading;
4+
export let log = [];
5+
6+
$: log.push(activeHeading);
7+
</script>
8+
9+
<div class="tabs">
10+
<div class="tab-toggles">
11+
<button class:active={activeTab === 0} on:click={() => activeTab = 0}>Tab 1</button>
12+
<button class:active={activeTab === 1} on:click={() => activeTab = 1}>Tab 2</button>
13+
<button class:active={activeTab === 2} on:click={() => activeTab = 2}>Tab 3</button>
14+
</div>
15+
<div class="tab-content">
16+
{#if activeTab === 0}
17+
<div><h1 bind:this={activeHeading}>Tab 1</h1></div>
18+
{/if}
19+
{#if activeTab === 1}
20+
<div><h1 bind:this={activeHeading}>Tab 2</h1></div>
21+
{/if}
22+
{#if activeTab === 2}
23+
<div><h1 bind:this={activeHeading}>Tab 3</h1></div>
24+
{/if}
25+
</div>
26+
<duiv>
27+
</div>

0 commit comments

Comments
 (0)