Repository: sveltejs/svelte (JavaScript)
Date: 2019-01-05 19:16:18+00:00
Task type: Bug Fix

Issue:
document mouseenter/mouseleave not fired on Firefox
It seems that Firefox does not fire `mouseenter`/`mouseleave` events on the document, so the new `<svelte:document on:mouseenter/mouseleave>` does not work there. It looks like they are fired on `document.body`, and they are also fired there on Chrome.

I know the whole point of `<svelte:document>` was to allow attaching these specific event handlers, but that's not cross-platform. The sanest thing might be to say 'ha, whoops, well don't use that' and to introduce a `<svelte:body>` that lets you attach handlers to the body. I'm not sure.


Issue discussion:
I think we should create a svelte special component for each unique html tag and useful global object.

Let's see, we already have and in plans:

```html
<svelte:self>
<svelte:component/>
<svelte:window />
<svelte:document />
<svelte:head />
<svelte:meta />
```
We missed following possible components:

```html
<svelte:html />
<svelte:body />
<svelte:navigator /> 
<svelte:location />
<svelte:history />
```

More fun with special components! 😄 
This explains https://github.com/sveltejs/svelte.technology/pull/376

I tried adding the new `<svelte:document>` to the docs and the example I created wasn't working in the REPL in Firefox…

I will try and do some research on this.

Solution patch:
diff --git a/site/content/guide/06-special-components.md b/site/content/guide/06-special-components.md
--- a/site/content/guide/06-special-components.md
+++ b/site/content/guide/06-special-components.md
@@ -114,11 +114,9 @@ You can also bind to certain values — so far `innerWidth`, `outerWidth`, `inne
 ```
 
 
-### `<svelte:document>`
+### `<svelte:body>`
 
-TODO REPLACE THIS WITH svelte:body
-
-The `<svelte:document>` tag, just like `<svelte:window>`, gives you a convenient way to declaratively add event listeners to the `document` object. This is useful for listening to events that don't fire on `window`, such as `mouseenter` and `mouseleave`.
+The `<svelte:body>` tag, just like `<svelte:window>`, gives you a convenient way to declaratively add event listeners to the `document.body` object. This is useful for listening to events that don't fire on `window`, such as `mouseenter` and `mouseleave`.
 
 
 ### `<svelte:head>`
diff --git a/src/compile/nodes/Document.ts b/src/compile/nodes/Body.ts
similarity index 87%
rename from src/compile/nodes/Document.ts
rename to src/compile/nodes/Body.ts
--- a/src/compile/nodes/Document.ts
+++ b/src/compile/nodes/Body.ts
@@ -1,8 +1,8 @@
 import Node from './shared/Node';
 import EventHandler from './EventHandler';
 
-export default class Document extends Node {
-	type: 'Document';
+export default class Body extends Node {
+	type: 'Body';
 	handlers: EventHandler[];
 
 	constructor(component, parent, scope, info) {
diff --git a/src/compile/nodes/shared/mapChildren.ts b/src/compile/nodes/shared/mapChildren.ts
--- a/src/compile/nodes/shared/mapChildren.ts
+++ b/src/compile/nodes/shared/mapChildren.ts
@@ -1,6 +1,6 @@
 import AwaitBlock from '../AwaitBlock';
+import Body from '../Body';
 import Comment from '../Comment';
-import Document from '../Document';
 import EachBlock from '../EachBlock';
 import Element from '../Element';
 import Head from '../Head';
@@ -19,8 +19,8 @@ import Node from './Node';
 function getConstructor(type): typeof Node {
 	switch (type) {
 		case 'AwaitBlock': return AwaitBlock;
+		case 'Body': return Body;
 		case 'Comment': return Comment;
-		case 'Document': return Document;
 		case 'EachBlock': return EachBlock;
 		case 'Element': return Element;
 		case 'Head': return Head;
@@ -50,4 +50,4 @@ export default function mapChildren(component, parent, scope, children: any[]) {
 
 		return node;
 	});
-}
\ No newline at end of file
+}
diff --git a/src/compile/render-dom/wrappers/Document.ts b/src/compile/render-dom/wrappers/Body.ts
similarity index 60%
rename from src/compile/render-dom/wrappers/Document.ts
rename to src/compile/render-dom/wrappers/Body.ts
--- a/src/compile/render-dom/wrappers/Document.ts
+++ b/src/compile/render-dom/wrappers/Body.ts
@@ -1,22 +1,22 @@
 import Block from '../Block';
 import Wrapper from './shared/Wrapper';
 import deindent from '../../../utils/deindent';
-import Document from '../../nodes/Document';
+import Body from '../../nodes/Body';
 
-export default class DocumentWrapper extends Wrapper {
-	node: Document;
+export default class BodyWrapper extends Wrapper {
+	node: Body;
 
 	render(block: Block, parentNode: string, parentNodes: string) {
 		this.node.handlers.forEach(handler => {
 			const snippet = handler.render(block);
 
 			block.builders.init.addBlock(deindent`
-				document.addEventListener("${handler.name}", ${snippet});
+				document.body.addEventListener("${handler.name}", ${snippet});
 			`);
 
 			block.builders.destroy.addBlock(deindent`
-				document.removeEventListener("${handler.name}", ${snippet});
+				document.body.removeEventListener("${handler.name}", ${snippet});
 			`);
 		});
 	}
-}
\ No newline at end of file
+}
diff --git a/src/compile/render-dom/wrappers/Fragment.ts b/src/compile/render-dom/wrappers/Fragment.ts
--- a/src/compile/render-dom/wrappers/Fragment.ts
+++ b/src/compile/render-dom/wrappers/Fragment.ts
@@ -1,7 +1,7 @@
 import Wrapper from './shared/Wrapper';
 import AwaitBlock from './AwaitBlock';
+import Body from './Body';
 import DebugTag from './DebugTag';
-import Document from './Document';
 import EachBlock from './EachBlock';
 import Element from './Element';
 import Head from './Head';
@@ -21,8 +21,8 @@ import Block from '../Block';
 
 const wrappers = {
 	AwaitBlock,
+	Body,
 	Comment: null,
-	Document,
 	DebugTag,
 	EachBlock,
 	Element,
@@ -140,4 +140,4 @@ export default class FragmentWrapper {
 			this.nodes[i].render(block, parentNode, parentNodes);
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/src/compile/render-ssr/Renderer.ts b/src/compile/render-ssr/Renderer.ts
--- a/src/compile/render-ssr/Renderer.ts
+++ b/src/compile/render-ssr/Renderer.ts
@@ -19,9 +19,9 @@ function noop(){}
 
 const handlers: Record<string, Handler> = {
 	AwaitBlock,
+	Body: noop,
 	Comment,
 	DebugTag,
-	Document: noop,
 	EachBlock,
 	Element,
 	Head,
@@ -64,4 +64,4 @@ export default class Renderer {
 			handler(node, this, options);
 		});
 	}
-}
\ No newline at end of file
+}
diff --git a/src/parse/state/tag.ts b/src/parse/state/tag.ts
--- a/src/parse/state/tag.ts
+++ b/src/parse/state/tag.ts
@@ -14,7 +14,7 @@ const metaTags = new Map([
 	['svelte:head', 'Head'],
 	['svelte:meta', 'Meta'],
 	['svelte:window', 'Window'],
-	['svelte:document', 'Document']
+	['svelte:body', 'Body']
 ]);
 
 const valid_meta_tags = [...metaTags.keys(), 'svelte:self', 'svelte:component'];
@@ -101,7 +101,7 @@ export default function tag(parser: Parser) {
 		const slug = metaTags.get(name).toLowerCase();
 		if (isClosingTag) {
 			if (
-				(name === 'svelte:window' || name === 'svelte:document') &&
+				(name === 'svelte:window' || name === 'svelte:body') &&
 				parser.current().children.length
 			) {
 				parser.error({
@@ -540,4 +540,4 @@ function readSequence(parser: Parser, done: () => boolean) {
 		code: `unexpected-eof`,
 		message: `Unexpected end of input`
 	});
-}
\ No newline at end of file
+}


Test patch:
diff --git a/test/parser/samples/error-svelte-selfdestructive/error.json b/test/parser/samples/error-svelte-selfdestructive/error.json
--- a/test/parser/samples/error-svelte-selfdestructive/error.json
+++ b/test/parser/samples/error-svelte-selfdestructive/error.json
@@ -1,10 +1,10 @@
 {
 	"code": "invalid-tag-name",
-	"message": "Valid <svelte:...> tag names are svelte:head, svelte:meta, svelte:window, svelte:document, svelte:self or svelte:component",
+	"message": "Valid <svelte:...> tag names are svelte:head, svelte:meta, svelte:window, svelte:body, svelte:self or svelte:component",
 	"pos": 10,
 	"start": {
 		"character": 10,
 		"line": 2,
 		"column": 2
 	}
-}
\ No newline at end of file
+}
diff --git a/test/runtime/samples/document-event/_config.js b/test/runtime/samples/document-event/_config.js
--- a/test/runtime/samples/document-event/_config.js
+++ b/test/runtime/samples/document-event/_config.js
@@ -1,13 +1,13 @@
 export default {
-	test({ assert, component, target, window }) {
+	test({ assert, component, window }) {
 		assert.deepEqual(component.events, []);
 
 		const event1 = new window.Event('mouseenter');
-		window.document.dispatchEvent(event1);
+		window.document.body.dispatchEvent(event1);
 		assert.deepEqual(component.events, ['enter']);
 
 		const event2 = new window.Event('mouseleave');
-		window.document.dispatchEvent(event2);
+		window.document.body.dispatchEvent(event2);
 		assert.deepEqual(component.events, ['enter', 'leave']);
 	},
-};
\ No newline at end of file
+};
diff --git a/test/runtime/samples/document-event/main.html b/test/runtime/samples/document-event/main.html
--- a/test/runtime/samples/document-event/main.html
+++ b/test/runtime/samples/document-event/main.html
@@ -6,4 +6,4 @@
 	}
 </script>
 
-<svelte:document on:mouseenter='{() => log("enter")}' on:mouseleave='{() => log("leave")}'/>
\ No newline at end of file
+<svelte:body on:mouseenter='{() => log("enter")}' on:mouseleave='{() => log("leave")}'/>