diff --git a/src/compile/nodes/Document.ts b/src/compile/nodes/Document.ts new file mode 100644 index 0000000000..48e985291c --- /dev/null +++ b/src/compile/nodes/Document.ts @@ -0,0 +1,23 @@ +import Node from './shared/Node'; +import EventHandler from './EventHandler'; + +export default class Document extends Node { + type: 'Document'; + handlers: EventHandler[]; + + constructor(component, parent, scope, info) { + super(component, parent, scope, info); + + this.handlers = []; + + info.attributes.forEach(node => { + if (node.type === 'EventHandler') { + this.handlers.push(new EventHandler(component, this, scope, node)); + } + + else { + // TODO there shouldn't be anything else here... + } + }); + } +} diff --git a/src/compile/nodes/shared/mapChildren.ts b/src/compile/nodes/shared/mapChildren.ts index 31133c6a51..a59705a8a7 100644 --- a/src/compile/nodes/shared/mapChildren.ts +++ b/src/compile/nodes/shared/mapChildren.ts @@ -1,5 +1,6 @@ import AwaitBlock from '../AwaitBlock'; import Comment from '../Comment'; +import Document from '../Document'; import EachBlock from '../EachBlock'; import Element from '../Element'; import Head from '../Head'; @@ -18,6 +19,7 @@ function getConstructor(type): typeof Node { switch (type) { case 'AwaitBlock': return AwaitBlock; case 'Comment': return Comment; + case 'Document': return Document; case 'EachBlock': return EachBlock; case 'Element': return Element; case 'Head': return Head; diff --git a/src/compile/render-dom/wrappers/Document.ts b/src/compile/render-dom/wrappers/Document.ts new file mode 100644 index 0000000000..b707c63ab4 --- /dev/null +++ b/src/compile/render-dom/wrappers/Document.ts @@ -0,0 +1,62 @@ +import Renderer from '../Renderer'; +import Block from '../Block'; +import Node from '../../nodes/shared/Node'; +import Wrapper from './shared/Wrapper'; +import deindent from '../../../utils/deindent'; +import Document from '../../nodes/Document'; + +export default class DocumentWrapper extends Wrapper { + node: Document; + + constructor(renderer: Renderer, block: Block, parent: Wrapper, node: Node) { + super(renderer, block, parent, node); + } + + render(block: Block, parentNode: string, parentNodes: string) { + const { renderer } = this; + const { component } = renderer; + + this.node.handlers.forEach(handler => { + // TODO verify that it's a valid callee (i.e. built-in or declared method) + component.addSourcemapLocations(handler.expression); + + const isCustomEvent = component.events.has(handler.name); + + let usesState = handler.dependencies.size > 0; + + handler.render(component, block, 'document', false); // TODO hoist? + + const handlerName = block.getUniqueName(`onwindow${handler.name}`); + const handlerBody = deindent` + ${usesState && `var ctx = #component.get();`} + ${handler.snippet}; + `; + + if (isCustomEvent) { + // TODO dry this out + block.addVariable(handlerName); + + block.builders.hydrate.addBlock(deindent` + ${handlerName} = %events-${handler.name}.call(#component, document, function(event) { + ${handlerBody} + }); + `); + + block.builders.destroy.addLine(deindent` + ${handlerName}.destroy(); + `); + } else { + block.builders.init.addBlock(deindent` + function ${handlerName}(event) { + ${handlerBody} + } + document.addEventListener("${handler.name}", ${handlerName}); + `); + + block.builders.destroy.addBlock(deindent` + document.removeEventListener("${handler.name}", ${handlerName}); + `); + } + }); + } +} \ No newline at end of file diff --git a/src/compile/render-dom/wrappers/Fragment.ts b/src/compile/render-dom/wrappers/Fragment.ts index ec1c83d1d9..a766fd002a 100644 --- a/src/compile/render-dom/wrappers/Fragment.ts +++ b/src/compile/render-dom/wrappers/Fragment.ts @@ -1,6 +1,7 @@ import Wrapper from './shared/Wrapper'; import AwaitBlock from './AwaitBlock'; import DebugTag from './DebugTag'; +import Document from './Document'; import EachBlock from './EachBlock'; import Element from './Element'; import Head from './Head'; @@ -21,6 +22,7 @@ import Block from '../Block'; const wrappers = { AwaitBlock, Comment: null, + Document, DebugTag, EachBlock, Element, diff --git a/src/compile/render-ssr/Renderer.ts b/src/compile/render-ssr/Renderer.ts index 525bd4e1ce..4f99b9de62 100644 --- a/src/compile/render-ssr/Renderer.ts +++ b/src/compile/render-ssr/Renderer.ts @@ -21,6 +21,7 @@ const handlers: Record = { AwaitBlock, Comment, DebugTag, + Document: noop, EachBlock, Element, Head, diff --git a/src/parse/state/tag.ts b/src/parse/state/tag.ts index 4dba778c88..f35cb204f3 100644 --- a/src/parse/state/tag.ts +++ b/src/parse/state/tag.ts @@ -11,6 +11,7 @@ import { Node } from '../../interfaces'; const validTagName = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/; const metaTags = new Map([ + ['svelte:document', 'Document'], ['svelte:window', 'Window'], ['svelte:head', 'Head'] ]); @@ -96,9 +97,12 @@ export default function tag(parser: Parser) { if (metaTags.has(name)) { const slug = metaTags.get(name).toLowerCase(); if (isClosingTag) { - if (name === 'svelte:window' && parser.current().children.length) { + if ( + (name === 'svelte:window' || name === 'svelte:document') && + parser.current().children.length + ) { parser.error({ - code: `invalid-window-content`, + code: `invalid-${name.slice(7)}-content`, message: `<${name}> cannot have children` }, parser.current().children[0].start); } diff --git a/test/runtime/samples/document-event/_config.js b/test/runtime/samples/document-event/_config.js new file mode 100644 index 0000000000..946ba63ee7 --- /dev/null +++ b/test/runtime/samples/document-event/_config.js @@ -0,0 +1,11 @@ +export default { + test(assert, component, target, window) { + assert.equal(component.get().events.toString(), ''); + const event1 = new window.Event('mouseenter'); + window.document.dispatchEvent(event1); + assert.equal(component.get().events.toString(), 'enter'); + const event2 = new window.Event('mouseleave'); + window.document.dispatchEvent(event2); + assert.equal(component.get().events.toString(), '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 new file mode 100644 index 0000000000..4e6aa93512 --- /dev/null +++ b/test/runtime/samples/document-event/main.html @@ -0,0 +1,12 @@ + + + \ No newline at end of file