add a <svelte:document> tag - #1484

pull/1813/head
Rich Harris 6 years ago
parent d1f35dfd85
commit db37e3a84b

@ -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...
}
});
}
}

@ -1,5 +1,6 @@
import AwaitBlock from '../AwaitBlock'; import AwaitBlock from '../AwaitBlock';
import Comment from '../Comment'; import Comment from '../Comment';
import Document from '../Document';
import EachBlock from '../EachBlock'; import EachBlock from '../EachBlock';
import Element from '../Element'; import Element from '../Element';
import Head from '../Head'; import Head from '../Head';
@ -18,6 +19,7 @@ function getConstructor(type): typeof Node {
switch (type) { switch (type) {
case 'AwaitBlock': return AwaitBlock; case 'AwaitBlock': return AwaitBlock;
case 'Comment': return Comment; case 'Comment': return Comment;
case 'Document': return Document;
case 'EachBlock': return EachBlock; case 'EachBlock': return EachBlock;
case 'Element': return Element; case 'Element': return Element;
case 'Head': return Head; case 'Head': return Head;

@ -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});
`);
}
});
}
}

@ -1,6 +1,7 @@
import Wrapper from './shared/Wrapper'; import Wrapper from './shared/Wrapper';
import AwaitBlock from './AwaitBlock'; import AwaitBlock from './AwaitBlock';
import DebugTag from './DebugTag'; import DebugTag from './DebugTag';
import Document from './Document';
import EachBlock from './EachBlock'; import EachBlock from './EachBlock';
import Element from './Element'; import Element from './Element';
import Head from './Head'; import Head from './Head';
@ -21,6 +22,7 @@ import Block from '../Block';
const wrappers = { const wrappers = {
AwaitBlock, AwaitBlock,
Comment: null, Comment: null,
Document,
DebugTag, DebugTag,
EachBlock, EachBlock,
Element, Element,

@ -21,6 +21,7 @@ const handlers: Record<string, Handler> = {
AwaitBlock, AwaitBlock,
Comment, Comment,
DebugTag, DebugTag,
Document: noop,
EachBlock, EachBlock,
Element, Element,
Head, Head,

@ -11,6 +11,7 @@ import { Node } from '../../interfaces';
const validTagName = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/; const validTagName = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/;
const metaTags = new Map([ const metaTags = new Map([
['svelte:document', 'Document'],
['svelte:window', 'Window'], ['svelte:window', 'Window'],
['svelte:head', 'Head'] ['svelte:head', 'Head']
]); ]);
@ -96,9 +97,12 @@ export default function tag(parser: Parser) {
if (metaTags.has(name)) { if (metaTags.has(name)) {
const slug = metaTags.get(name).toLowerCase(); const slug = metaTags.get(name).toLowerCase();
if (isClosingTag) { if (isClosingTag) {
if (name === 'svelte:window' && parser.current().children.length) { if (
(name === 'svelte:window' || name === 'svelte:document') &&
parser.current().children.length
) {
parser.error({ parser.error({
code: `invalid-window-content`, code: `invalid-${name.slice(7)}-content`,
message: `<${name}> cannot have children` message: `<${name}> cannot have children`
}, parser.current().children[0].start); }, parser.current().children[0].start);
} }

@ -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');
},
};

@ -0,0 +1,12 @@
<svelte:document on:mouseenter='log("enter")' on:mouseleave='log("leave")'/>
<script>
export default {
data: () => ({ events: [] }),
methods: {
log(event) {
this.set({ events: this.get().events.concat(event) });
},
},
};
</script>
Loading…
Cancel
Save