Repository: sveltejs/svelte (JavaScript)
Date: 2018-04-04 13:13:33+00:00
Task type: Bug Fix

Issue:
Custom events not working on <:Window>
Custom events are not being respected (used) when listening to them on `<:Window>`. The repl wasn't saving for me just now so you can copy-paste this into it to see. It gives an error about the `escKey` custom event being unused and in the source you can see it has `window.addEventListener('escKey', onwindowescKey)`.

```html
<:Window on:escKey="handleEsc(event)"/>

<script>
  export default {
    events: {
      escKey(node, callback) {
        function onKeyDown(event) {
          if (event.keyCode === 27) callback(event);
        }
        node.addEventListener('keydown', onKeyDown);
        return {
          teardown() {
            node.removeEventListener('keydown', onKeyDown);
          }
        }
      }
    },
    methods: {
      handleEsc(event) {
        console.log(event)
      },
    }
  }
</script>
```


Issue discussion:

Solution patch:
diff --git a/src/generators/nodes/Window.ts b/src/generators/nodes/Window.ts
--- a/src/generators/nodes/Window.ts
+++ b/src/generators/nodes/Window.ts
@@ -51,6 +51,8 @@ export default class Window extends Node {
 				// TODO verify that it's a valid callee (i.e. built-in or declared method)
 				generator.addSourcemapLocations(attribute.expression);
 
+				const isCustomEvent = generator.events.has(attribute.name);
+
 				let usesState = false;
 
 				attribute.expression.arguments.forEach((arg: Node) => {
@@ -74,16 +76,39 @@ export default class Window extends Node {
 					[✂${attribute.expression.start}-${attribute.expression.end}✂];
 				`;
 
-				block.builders.init.addBlock(deindent`
-					function ${handlerName}(event) {
-						${handlerBody}
+				if (isCustomEvent) {
+					// TODO dry this out
+					block.addVariable(handlerName);
+
+					block.builders.hydrate.addBlock(deindent`
+						${handlerName} = %events-${attribute.name}.call(#component, window, function(event) {
+							${handlerBody}
+						});
+					`);
+
+					if (generator.options.dev) {
+						block.builders.hydrate.addBlock(deindent`
+							if (${handlerName}.teardown) {
+								console.warn("Return 'destroy()' from custom event handlers. Returning 'teardown()' has been deprecated and will be unsupported in Svelte 2");
+							}
+						`);
 					}
-					window.addEventListener("${attribute.name}", ${handlerName});
-				`);
 
-				block.builders.destroy.addBlock(deindent`
-					window.removeEventListener("${attribute.name}", ${handlerName});
-				`);
+					block.builders.destroy.addLine(deindent`
+						${handlerName}[${handlerName}.destroy ? 'destroy' : 'teardown']();
+					`);
+				} else {
+					block.builders.init.addBlock(deindent`
+						function ${handlerName}(event) {
+							${handlerBody}
+						}
+						window.addEventListener("${attribute.name}", ${handlerName});
+					`);
+
+					block.builders.destroy.addBlock(deindent`
+						window.removeEventListener("${attribute.name}", ${handlerName});
+					`);
+				}
 			}
 
 			if (attribute.type === 'Binding') {
diff --git a/src/validate/html/validateWindow.ts b/src/validate/html/validateWindow.ts
--- a/src/validate/html/validateWindow.ts
+++ b/src/validate/html/validateWindow.ts
@@ -51,6 +51,7 @@ export default function validateWindow(validator: Validator, node: Node, refs: M
 				}
 			}
 		} else if (attribute.type === 'EventHandler') {
+			validator.used.events.add(attribute.name);
 			validateEventHandler(validator, attribute, refCallees);
 		}
 	});


Test patch:
diff --git a/test/runtime/samples/window-event-custom/_config.js b/test/runtime/samples/window-event-custom/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/window-event-custom/_config.js
@@ -0,0 +1,15 @@
+export default {
+	html: `<p>escaped: false</p>`,
+
+	test(assert, component, target, window) {
+		const event = new window.KeyboardEvent('keydown', {
+			which: 27
+		});
+
+		window.dispatchEvent(event);
+
+		assert.htmlEqual(target.innerHTML, `
+			<p>escaped: true</p>
+		`);
+	},
+};
diff --git a/test/runtime/samples/window-event-custom/main.html b/test/runtime/samples/window-event-custom/main.html
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/window-event-custom/main.html
@@ -0,0 +1,25 @@
+<:Window on:esc="set({ escaped: true })" />
+
+<p>escaped: {{escaped}}</p>
+
+<script>
+	export default {
+		data() {
+			return { escaped: false };
+		},
+
+		events: {
+			esc(node, callback) {
+				function onKeyDown(event) {
+					if (event.which === 27) callback(event);
+				}
+				node.addEventListener('keydown', onKeyDown);
+				return {
+					destroy() {
+						node.removeEventListener('keydown', onKeyDown);
+					}
+				};
+			}
+		}
+	};
+</script>
\ No newline at end of file