Skip to content

Commit 92f1b48

Browse files
author
Timothy Johnson
committed
Mechanism for bubbling all events. Closes #2837
1 parent e606a0c commit 92f1b48

File tree

11 files changed

+127
-28
lines changed

11 files changed

+127
-28
lines changed

src/compiler/compile/nodes/EventHandler.ts

-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import Node from './shared/Node';
22
import Expression from './shared/Expression';
33
import Component from '../Component';
4-
import { sanitize } from '../../utils/names';
54
import { Identifier } from 'estree';
65

76
export default class EventHandler extends Node {
@@ -42,8 +41,6 @@ export default class EventHandler extends Node {
4241
}
4342
}
4443
}
45-
} else {
46-
this.handler_name = component.get_unique_name(`${sanitize(this.name)}_handler`);
4744
}
4845
}
4946

src/compiler/compile/render_dom/Block.ts

+30-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Renderer from './Renderer';
22
import Wrapper from './wrappers/shared/Wrapper';
3-
import { b, x } from 'code-red';
3+
import { b, x, p } from 'code-red';
44
import { Node, Identifier, ArrayPattern } from 'estree';
55
import { is_head } from './wrappers/shared/is_head';
66

@@ -52,6 +52,7 @@ export default class Block {
5252
claim: Array<Node | Node[]>;
5353
hydrate: Array<Node | Node[]>;
5454
mount: Array<Node | Node[]>;
55+
bubble: Array<Node | Node[]>;
5556
measure: Array<Node | Node[]>;
5657
fix: Array<Node | Node[]>;
5758
animate: Array<Node | Node[]>;
@@ -100,6 +101,7 @@ export default class Block {
100101
claim: [],
101102
hydrate: [],
102103
mount: [],
104+
bubble: [],
103105
measure: [],
104106
fix: [],
105107
animate: [],
@@ -292,12 +294,27 @@ export default class Block {
292294
}`;
293295
}
294296

297+
if (this.chunks.bubble.length > 0) {
298+
const bubble_fns: Identifier = {
299+
type: 'Identifier',
300+
name: '#bubble_fns'
301+
};
302+
this.add_variable(bubble_fns, x`[]`);
303+
304+
properties.bubble = x`function #bubble(type, callback) {
305+
const local_dispose = [];
306+
const fn = () => {
307+
${this.chunks.bubble}
308+
#dispose.push(...local_dispose);
309+
}
310+
if (#mounted) fn()
311+
else ${bubble_fns}.push(fn);
312+
return () => @run_all(local_dispose);
313+
}`;
314+
}
315+
295316
if (this.chunks.mount.length === 0) {
296317
properties.mount = noop;
297-
} else if (this.event_listeners.length === 0) {
298-
properties.mount = x`function #mount(#target, #anchor) {
299-
${this.chunks.mount}
300-
}`;
301318
} else {
302319
properties.mount = x`function #mount(#target, #anchor) {
303320
${this.chunks.mount}
@@ -387,6 +404,10 @@ export default class Block {
387404
d: ${properties.destroy}
388405
}`;
389406

407+
if (properties.bubble) {
408+
return_value.properties.push(p`b: ${properties.bubble}`);
409+
}
410+
390411
const block = dev && this.get_unique_name('block');
391412

392413
const body = b`
@@ -428,6 +449,7 @@ export default class Block {
428449
this.chunks.hydrate.length > 0 ||
429450
this.chunks.claim.length > 0 ||
430451
this.chunks.mount.length > 0 ||
452+
this.chunks.bubble.length > 0 ||
431453
this.chunks.update.length > 0 ||
432454
this.chunks.destroy.length > 0 ||
433455
this.has_animation;
@@ -451,7 +473,7 @@ export default class Block {
451473
}
452474

453475
render_listeners(chunk: string = '') {
454-
if (this.event_listeners.length > 0) {
476+
if (this.event_listeners.length > 0 || this.chunks.bubble.length > 0) {
455477
this.add_variable({ type: 'Identifier', name: '#mounted' });
456478
this.chunks.destroy.push(b`#mounted = false`);
457479

@@ -462,7 +484,7 @@ export default class Block {
462484

463485
this.add_variable(dispose);
464486

465-
if (this.event_listeners.length === 1) {
487+
if (this.event_listeners.length === 1 && this.chunks.bubble.length === 0) {
466488
this.chunks.mount.push(
467489
b`
468490
if (!#mounted) {
@@ -481,6 +503,7 @@ export default class Block {
481503
${dispose} = [
482504
${this.event_listeners}
483505
];
506+
${this.chunks.bubble.length > 0 && b`@run_all(#bubble_fns)`}
484507
#mounted = true;
485508
}
486509
`);

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

+10-11
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,10 @@ export default class EventHandlerWrapper {
1414
constructor(node: EventHandler, parent: Wrapper) {
1515
this.node = node;
1616
this.parent = parent;
17-
18-
if (!node.expression) {
19-
this.parent.renderer.add_to_context(node.handler_name.name);
20-
21-
this.parent.renderer.component.partly_hoisted.push(b`
22-
function ${node.handler_name.name}(event) {
23-
@bubble($$self, event);
24-
}
25-
`);
26-
}
2717
}
2818

2919
get_snippet(block) {
30-
const snippet = this.node.expression ? this.node.expression.manipulate(block) : block.renderer.reference(this.node.handler_name);
20+
const snippet = this.node.expression.manipulate(block);
3121

3222
if (this.node.reassigned) {
3323
block.maintain_context = true;
@@ -37,6 +27,15 @@ export default class EventHandlerWrapper {
3727
}
3828

3929
render(block: Block, target: string | Expression) {
30+
if (!this.node.expression) {
31+
if (this.node.name === "*")
32+
block.chunks.bubble.push(b`local_dispose.push(@listen(${target}, type, callback))`);
33+
else
34+
block.chunks.bubble.push(b`if (type === "${this.node.name}") local_dispose.push(@listen(${target}, "${this.node.name}", callback));`);
35+
36+
return;
37+
}
38+
4039
let snippet = this.get_snippet(block);
4140

4241
if (this.node.modifiers.has('preventDefault')) snippet = x`@prevent_default(${snippet})`;

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

+18-7
Original file line numberDiff line numberDiff line change
@@ -391,13 +391,24 @@ export default class InlineComponentWrapper extends Wrapper {
391391
return b`@binding_callbacks.push(() => @bind(${this.var}, '${binding.name}', ${id}));`;
392392
});
393393

394-
const munged_handlers = this.node.handlers.map(handler => {
395-
const event_handler = new EventHandler(handler, this);
396-
let snippet = event_handler.get_snippet(block);
397-
if (handler.modifiers.has('once')) snippet = x`@once(${snippet})`;
398-
399-
return b`${name}.$on("${handler.name}", ${snippet});`;
400-
});
394+
const munged_handlers = this.node.handlers
395+
.filter(handler => {
396+
if (handler.expression) return true;
397+
398+
if (handler.name === "*")
399+
block.chunks.bubble.push(b`local_dispose.push(${name}.$on(type, callback))`);
400+
else
401+
block.chunks.bubble.push(b`if (type === "${handler.name}") local_dispose.push(${name}.$on("${handler.name}", callback));`);
402+
403+
return false;
404+
})
405+
.map(handler => {
406+
const event_handler = new EventHandler(handler, this);
407+
let snippet = event_handler.get_snippet(block);
408+
if (handler.modifiers.has('once')) snippet = x`@once(${snippet})`;
409+
410+
return b`${name}.$on("${handler.name}", ${snippet});`;
411+
});
401412

402413
if (this.node.name === 'svelte:component') {
403414
const switch_value = block.get_unique_name('switch_value');

src/runtime/internal/Component.ts

+6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ interface Fragment {
1111
/* claim */ l: (nodes: any) => void;
1212
/* hydrate */ h: () => void;
1313
/* mount */ m: (target: HTMLElement, anchor: any) => void;
14+
/* bubble */ b: (type: string, callback: Function) => Function;
1415
/* update */ p: (ctx: any, dirty: any) => void;
1516
/* measure */ r: () => void;
1617
/* fix */ f: () => void;
@@ -215,10 +216,15 @@ export class SvelteComponent {
215216
}
216217

217218
$on(type, callback) {
219+
const dispose = this.$$.fragment
220+
&& this.$$.fragment.b
221+
&& this.$$.fragment.b(type, callback)
222+
|| noop;
218223
const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = []));
219224
callbacks.push(callback);
220225

221226
return () => {
227+
dispose();
222228
const index = callbacks.indexOf(callback);
223229
if (index !== -1) callbacks.splice(index, 1);
224230
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script>
2+
import { createEventDispatcher } from 'svelte';
3+
4+
const dispatch = createEventDispatcher();
5+
</script>
6+
7+
<button on:click='{() => dispatch("foo", { answer: 42 })}'>click me</button>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export default {
2+
html: `
3+
<button>click me</button>
4+
`,
5+
6+
test({ assert, component, target, window }) {
7+
const button = target.querySelector('button');
8+
const event = new window.MouseEvent('click');
9+
10+
let answer;
11+
component.$on('foo', event => {
12+
answer = event.detail.answer;
13+
});
14+
15+
button.dispatchEvent(event);
16+
assert.equal(answer, 42);
17+
}
18+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
import Widget from './Widget.svelte';
3+
</script>
4+
5+
<Widget on:*/>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<button on:*>toggle</button>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export default {
2+
html: `
3+
<button>toggle</button>
4+
`,
5+
6+
async test({ assert, component, target, window }) {
7+
const button = target.querySelector('button');
8+
const event = new window.MouseEvent('click');
9+
10+
await button.dispatchEvent(event);
11+
assert.htmlEqual(target.innerHTML, `
12+
<button>toggle</button>
13+
<p>hello!</p>
14+
`);
15+
16+
await button.dispatchEvent(event);
17+
assert.htmlEqual(target.innerHTML, `
18+
<button>toggle</button>
19+
`);
20+
}
21+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script>
2+
import Button from './Button.svelte'
3+
4+
export let visible;
5+
</script>
6+
7+
<Button on:click='{() => visible = !visible}'>toggle</Button>
8+
9+
{#if visible}
10+
<p>hello!</p>
11+
{/if}

0 commit comments

Comments
 (0)