Skip to content

Commit db8e980

Browse files
Timothy Johnsonantony
Timothy Johnson
authored andcommitted
Mechanism for bubbling all events. Closes #2837
1 parent 229a16d commit db8e980

File tree

12 files changed

+127
-39
lines changed

12 files changed

+127
-39
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

@@ -47,6 +47,7 @@ export default class Block {
4747
claim: Array<Node | Node[]>;
4848
hydrate: Array<Node | Node[]>;
4949
mount: Array<Node | Node[]>;
50+
bubble: Array<Node | Node[]>;
5051
measure: Array<Node | Node[]>;
5152
fix: Array<Node | Node[]>;
5253
animate: Array<Node | Node[]>;
@@ -95,6 +96,7 @@ export default class Block {
9596
claim: [],
9697
hydrate: [],
9798
mount: [],
99+
bubble: [],
98100
measure: [],
99101
fix: [],
100102
animate: [],
@@ -287,12 +289,27 @@ export default class Block {
287289
}`;
288290
}
289291

292+
if (this.chunks.bubble.length > 0) {
293+
const bubble_fns: Identifier = {
294+
type: 'Identifier',
295+
name: '#bubble_fns'
296+
};
297+
this.add_variable(bubble_fns, x`[]`);
298+
299+
properties.bubble = x`function #bubble(type, callback) {
300+
const local_dispose = [];
301+
const fn = () => {
302+
${this.chunks.bubble}
303+
#dispose.push(...local_dispose);
304+
}
305+
if (#mounted) fn()
306+
else ${bubble_fns}.push(fn);
307+
return () => @run_all(local_dispose);
308+
}`;
309+
}
310+
290311
if (this.chunks.mount.length === 0) {
291312
properties.mount = noop;
292-
} else if (this.event_listeners.length === 0) {
293-
properties.mount = x`function #mount(#target, #anchor) {
294-
${this.chunks.mount}
295-
}`;
296313
} else {
297314
properties.mount = x`function #mount(#target, #anchor) {
298315
${this.chunks.mount}
@@ -382,6 +399,10 @@ export default class Block {
382399
d: ${properties.destroy}
383400
}`;
384401

402+
if (properties.bubble) {
403+
return_value.properties.push(p`b: ${properties.bubble}`);
404+
}
405+
385406
const block = dev && this.get_unique_name('block');
386407

387408
const body = b`
@@ -423,6 +444,7 @@ export default class Block {
423444
this.chunks.hydrate.length > 0 ||
424445
this.chunks.claim.length > 0 ||
425446
this.chunks.mount.length > 0 ||
447+
this.chunks.bubble.length > 0 ||
426448
this.chunks.update.length > 0 ||
427449
this.chunks.destroy.length > 0 ||
428450
this.has_animation;
@@ -446,7 +468,7 @@ export default class Block {
446468
}
447469

448470
render_listeners(chunk: string = '') {
449-
if (this.event_listeners.length > 0) {
471+
if (this.event_listeners.length > 0 || this.chunks.bubble.length > 0) {
450472
this.add_variable({ type: 'Identifier', name: '#mounted' });
451473
this.chunks.destroy.push(b`#mounted = false`);
452474

@@ -457,7 +479,7 @@ export default class Block {
457479

458480
this.add_variable(dispose);
459481

460-
if (this.event_listeners.length === 1) {
482+
if (this.event_listeners.length === 1 && this.chunks.bubble.length === 0) {
461483
this.chunks.mount.push(
462484
b`
463485
if (!#mounted) {
@@ -476,6 +498,7 @@ export default class Block {
476498
${dispose} = [
477499
${this.event_listeners}
478500
];
501+
${this.chunks.bubble.length > 0 && b`@run_all(#bubble_fns)`}
479502
#mounted = true;
480503
}
481504
`);

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
@@ -388,13 +388,24 @@ export default class InlineComponentWrapper extends Wrapper {
388388
return b`@binding_callbacks.push(() => @bind(${this.var}, '${binding.name}', ${id}));`;
389389
});
390390

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

399410
if (this.node.name === 'svelte:component') {
400411
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;
@@ -222,10 +223,15 @@ export class SvelteComponent {
222223
}
223224

224225
$on(type, callback) {
226+
const dispose = this.$$.fragment
227+
&& this.$$.fragment.b
228+
&& this.$$.fragment.b(type, callback)
229+
|| noop;
225230
const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = []));
226231
callbacks.push(callback);
227232

228233
return () => {
234+
dispose();
229235
const index = callbacks.indexOf(callback);
230236
if (index !== -1) callbacks.splice(index, 1);
231237
};

src/runtime/internal/lifecycle.ts

-11
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,3 @@ export function setContext<T>(key, context: T) {
5353
export function getContext<T>(key): T {
5454
return get_current_component().$$.context.get(key);
5555
}
56-
57-
// TODO figure out if we still want to support
58-
// shorthand events, or if we want to implement
59-
// a real bubbling mechanism
60-
export function bubble(component, event) {
61-
const callbacks = component.$$.callbacks[event.type];
62-
63-
if (callbacks) {
64-
callbacks.slice().forEach(fn => fn(event));
65-
}
66-
}
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)