Skip to content

Commit 105919e

Browse files
committed
feat dynamic event handler
1 parent 2045165 commit 105919e

File tree

18 files changed

+368
-79
lines changed

18 files changed

+368
-79
lines changed
Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import Node from './shared/Node';
22
import Expression from './shared/Expression';
33
import Component from '../Component';
4-
import { b, x } from 'code-red';
5-
import Block from '../render_dom/Block';
64
import { sanitize } from '../../utils/names';
75
import { Identifier } from 'estree';
86

@@ -14,6 +12,7 @@ export default class EventHandler extends Node {
1412
handler_name: Identifier;
1513
uses_context = false;
1614
can_make_passive = false;
15+
reassigned?: boolean;
1716

1817
constructor(component: Component, parent, template_scope, info) {
1918
super(component, parent, template_scope, info);
@@ -22,7 +21,7 @@ export default class EventHandler extends Node {
2221
this.modifiers = new Set(info.modifiers);
2322

2423
if (info.expression) {
25-
this.expression = new Expression(component, this, template_scope, info.expression, true);
24+
this.expression = new Expression(component, this, template_scope, info.expression);
2625
this.uses_context = this.expression.uses_context;
2726

2827
if (/FunctionExpression/.test(info.expression.type) && info.expression.params.length === 0) {
@@ -42,34 +41,12 @@ export default class EventHandler extends Node {
4241
if (node && (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' || node.type === 'ArrowFunctionExpression') && node.params.length === 0) {
4342
this.can_make_passive = true;
4443
}
44+
45+
this.reassigned = component.var_lookup.get(info.expression.name).reassigned;
4546
}
4647
}
4748
} else {
48-
const id = component.get_unique_name(`${sanitize(this.name)}_handler`);
49-
50-
component.add_var({
51-
name: id.name,
52-
internal: true,
53-
referenced: true
54-
});
55-
56-
component.partly_hoisted.push(b`
57-
function ${id}(event) {
58-
@bubble($$self, event);
59-
}
60-
`);
61-
62-
this.handler_name = id;
49+
this.handler_name = component.get_unique_name(`${sanitize(this.name)}_handler`);
6350
}
6451
}
65-
66-
// TODO move this? it is specific to render-dom
67-
render(block: Block) {
68-
if (this.expression) {
69-
return this.expression.manipulate(block);
70-
}
71-
72-
// this.component.add_reference(this.handler_name);
73-
return x`#ctx.${this.handler_name}`;
74-
}
7552
}

src/compiler/compile/render_dom/Block.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export default class Block {
6060
destroy: Array<Node | Node[]>;
6161
};
6262

63-
event_listeners: Node[] = [];
63+
event_listeners: Array<Node> = [];
6464

6565
maintain_context: boolean;
6666
has_animation: boolean;
@@ -203,13 +203,11 @@ export default class Block {
203203
}
204204

205205
add_variable(id: Identifier, init?: Node) {
206-
this.variables.forEach(v => {
207-
if (v.id.name === id.name) {
208-
throw new Error(
209-
`Variable '${id.name}' already initialised with a different value`
210-
);
211-
}
212-
});
206+
if (this.variables.has(id.name)) {
207+
throw new Error(
208+
`Variable '${id.name}' already initialised with a different value`
209+
);
210+
}
213211

214212
this.variables.set(id.name, { id, init });
215213
}

src/compiler/compile/render_dom/wrappers/Body.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,24 @@ import Wrapper from './shared/Wrapper';
33
import { b } from 'code-red';
44
import Body from '../../nodes/Body';
55
import { Identifier } from 'estree';
6+
import EventHandler from './Element/EventHandler';
67

78
export default class BodyWrapper extends Wrapper {
89
node: Body;
910

1011
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
11-
this.node.handlers.forEach(handler => {
12-
const snippet = handler.render(block);
12+
this.node.handlers
13+
.map(handler => new EventHandler(handler, this))
14+
.forEach(handler => {
15+
const snippet = handler.get_snippet(block);
1316

14-
block.chunks.init.push(b`
15-
@_document.body.addEventListener("${handler.name}", ${snippet});
16-
`);
17+
block.chunks.init.push(b`
18+
@_document.body.addEventListener("${handler.node.name}", ${snippet});
19+
`);
1720

18-
block.chunks.destroy.push(b`
19-
@_document.body.removeEventListener("${handler.name}", ${snippet});
20-
`);
21-
});
21+
block.chunks.destroy.push(b`
22+
@_document.body.removeEventListener("${handler.node.name}", ${snippet});
23+
`);
24+
});
2225
}
2326
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import EventHandler from '../../../nodes/EventHandler';
2+
import Wrapper from '../shared/Wrapper';
3+
import Block from '../../Block';
4+
import { b, x, p } from 'code-red';
5+
6+
const TRUE = x`true`;
7+
const FALSE = x`false`;
8+
9+
export default class EventHandlerWrapper {
10+
node: EventHandler;
11+
parent: Wrapper;
12+
13+
constructor(node: EventHandler, parent: Wrapper) {
14+
this.node = node;
15+
this.parent = parent;
16+
17+
if (!node.expression) {
18+
this.parent.renderer.component.add_var({
19+
name: node.handler_name.name,
20+
internal: true,
21+
referenced: true,
22+
});
23+
24+
this.parent.renderer.component.partly_hoisted.push(b`
25+
function ${node.handler_name.name}(event) {
26+
@bubble($$self, event);
27+
}
28+
`);
29+
}
30+
}
31+
32+
get_snippet(block) {
33+
const snippet = this.node.expression ? this.node.expression.manipulate(block) : x`#ctx.${this.node.handler_name}`;
34+
35+
if (this.node.reassigned) {
36+
block.maintain_context = true;
37+
return x`function () { ${snippet}.apply(this, arguments); }`;
38+
}
39+
return snippet;
40+
}
41+
42+
render(block: Block, target: string) {
43+
let snippet = this.get_snippet(block);
44+
45+
if (this.node.modifiers.has('preventDefault')) snippet = x`@prevent_default(${snippet})`;
46+
if (this.node.modifiers.has('stopPropagation')) snippet = x`@stop_propagation(${snippet})`;
47+
if (this.node.modifiers.has('self')) snippet = x`@self(${snippet})`;
48+
49+
const args = [];
50+
51+
const opts = ['passive', 'once', 'capture'].filter(mod => this.node.modifiers.has(mod));
52+
if (opts.length) {
53+
args.push((opts.length === 1 && opts[0] === 'capture')
54+
? TRUE
55+
: x`{ ${opts.map(opt => p`${opt}: true`)} }`);
56+
} else if (block.renderer.options.dev) {
57+
args.push(FALSE);
58+
}
59+
60+
if (block.renderer.options.dev) {
61+
args.push(this.node.modifiers.has('stopPropagation') ? TRUE : FALSE);
62+
args.push(this.node.modifiers.has('preventDefault') ? TRUE : FALSE);
63+
}
64+
65+
block.event_listeners.push(
66+
x`@listen(${target}, "${this.node.name}", ${snippet}, ${args})`
67+
);
68+
}
69+
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import bind_this from '../shared/bind_this';
2424
import { changed } from '../shared/changed';
2525
import { is_head } from '../shared/is_head';
2626
import { Identifier } from 'estree';
27+
import EventHandler from './EventHandler';
2728

2829
const events = [
2930
{
@@ -113,6 +114,7 @@ export default class ElementWrapper extends Wrapper {
113114
fragment: FragmentWrapper;
114115
attributes: AttributeWrapper[];
115116
bindings: Binding[];
117+
event_handlers: EventHandler[];
116118
class_dependencies: string[];
117119

118120
slot_block: Block;
@@ -194,6 +196,8 @@ export default class ElementWrapper extends Wrapper {
194196
// e.g. <audio bind:paused bind:currentTime>
195197
this.bindings = this.node.bindings.map(binding => new Binding(block, binding, this));
196198

199+
this.event_handlers = this.node.handlers.map(event_handler => new EventHandler(event_handler, this));
200+
197201
if (node.intro || node.outro) {
198202
if (node.intro) block.add_intro(node.intro.is_local);
199203
if (node.outro) block.add_outro(node.outro.is_local);
@@ -643,7 +647,7 @@ export default class ElementWrapper extends Wrapper {
643647
}
644648

645649
add_event_handlers(block: Block) {
646-
add_event_handlers(block, this.var, this.node.handlers);
650+
add_event_handlers(block, this.var, this.event_handlers);
647651
}
648652

649653
add_transitions(

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import is_dynamic from '../shared/is_dynamic';
1616
import bind_this from '../shared/bind_this';
1717
import { changed } from '../shared/changed';
1818
import { Node, Identifier, ObjectExpression } from 'estree';
19+
import EventHandler from '../Element/EventHandler';
1920

2021
export default class InlineComponentWrapper extends Wrapper {
2122
var: Identifier;
@@ -365,7 +366,8 @@ export default class InlineComponentWrapper extends Wrapper {
365366
});
366367

367368
const munged_handlers = this.node.handlers.map(handler => {
368-
let snippet = handler.render(block);
369+
const event_handler = new EventHandler(handler, this);
370+
let snippet = event_handler.get_snippet(block);
369371
if (handler.modifiers.has('once')) snippet = x`@once(${snippet})`;
370372

371373
return b`${name}.$on("${handler.name}", ${snippet});`;

src/compiler/compile/render_dom/wrappers/Window.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import add_actions from './shared/add_actions';
88
import { changed } from './shared/changed';
99
import { Identifier } from 'estree';
1010
import { TemplateNode } from '../../../interfaces';
11+
import EventHandler from './Element/EventHandler';
1112

1213
const associated_events = {
1314
innerWidth: 'resize',
@@ -34,9 +35,11 @@ const readonly = new Set([
3435

3536
export default class WindowWrapper extends Wrapper {
3637
node: Window;
38+
handlers: EventHandler[];
3739

3840
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) {
3941
super(renderer, block, parent, node);
42+
this.handlers = this.node.handlers.map(handler => new EventHandler(handler, this));
4043
}
4144

4245
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
@@ -47,7 +50,7 @@ export default class WindowWrapper extends Wrapper {
4750
const bindings: Record<string, string> = {};
4851

4952
add_actions(component, block, '@_window', this.node.actions);
50-
add_event_handlers(block, '@_window', this.node.handlers);
53+
add_event_handlers(block, '@_window', this.handlers);
5154

5255
this.node.bindings.forEach(binding => {
5356
// in dev mode, throw if read-only values are written to
Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,10 @@
11
import Block from '../../Block';
2-
import EventHandler from '../../../nodes/EventHandler';
3-
import { x, p } from 'code-red';
4-
5-
const TRUE = x`true`;
6-
const FALSE = x`false`;
2+
import EventHandler from '../Element/EventHandler';
73

84
export default function add_event_handlers(
95
block: Block,
106
target: string,
117
handlers: EventHandler[]
128
) {
13-
handlers.forEach(handler => {
14-
let snippet = handler.render(block);
15-
if (handler.modifiers.has('preventDefault')) snippet = x`@prevent_default(${snippet})`;
16-
if (handler.modifiers.has('stopPropagation')) snippet = x`@stop_propagation(${snippet})`;
17-
if (handler.modifiers.has('self')) snippet = x`@self(${snippet})`;
18-
19-
const args = [];
20-
21-
const opts = ['passive', 'once', 'capture'].filter(mod => handler.modifiers.has(mod));
22-
if (opts.length) {
23-
args.push((opts.length === 1 && opts[0] === 'capture')
24-
? TRUE
25-
: x`{ ${opts.map(opt => p`${opt}: true`)} }`);
26-
} else if (block.renderer.options.dev) {
27-
args.push(FALSE);
28-
}
29-
30-
if (block.renderer.options.dev) {
31-
args.push(handler.modifiers.has('stopPropagation') ? TRUE : FALSE);
32-
args.push(handler.modifiers.has('preventDefault') ? TRUE : FALSE);
33-
}
34-
35-
block.event_listeners.push(
36-
x`@listen(${target}, "${handler.name}", ${snippet}, ${args})`
37-
);
38-
});
9+
handlers.forEach(handler => handler.render(block, target));
3910
}

0 commit comments

Comments
 (0)