diff --git a/generate-type-definitions.js b/generate-type-definitions.js index 6ec2f313c337..4a36330b13bb 100644 --- a/generate-type-definitions.js +++ b/generate-type-definitions.js @@ -3,12 +3,7 @@ const { execSync } = require('child_process'); const { readFileSync, writeFileSync } = require('fs'); -try { - execSync('tsc -p src/compiler --emitDeclarationOnly && tsc -p src/runtime --emitDeclarationOnly'); -} catch (err) { - console.error(err.stderr.toString()); - throw err; -} +execSync('tsc -p src/compiler --emitDeclarationOnly && tsc -p src/runtime --emitDeclarationOnly', { stdio: 'inherit' }); // We need to add these types to the .d.ts files here because if we add them before building, the build will fail, // because the TS->JS transformation doesn't know these exports are types and produces code that fails at runtime. // We can't use `export type` syntax either because the TS version we're on doesn't have this feature yet. diff --git a/site/content/docs/03-template-syntax.md b/site/content/docs/03-template-syntax.md index ebc4ee8ef21e..95d76db5b7b0 100644 --- a/site/content/docs/03-template-syntax.md +++ b/site/content/docs/03-template-syntax.md @@ -1746,6 +1746,25 @@ All except `scrollX` and `scrollY` are readonly. > Note that the page will not be scrolled to the initial value to avoid accessibility issues. Only subsequent changes to the bound variable of `scrollX` and `scrollY` will cause scrolling. However, if the scrolling behaviour is desired, call `scrollTo()` in `onMount()`. +### `` + +```sv + +``` + +--- + +Similarly to ``, this element allows you to add listeners to events on `document`, such as `visibilitychange`, which don't fire on `window`. It also lets you use [actions](/docs#template-syntax-element-directives-use-action) on `document`. + +As with ``, this element may only appear the top level of your component and must never be inside a block or element. + +```sv + +``` + ### `` ```sv @@ -1756,7 +1775,7 @@ All except `scrollX` and `scrollY` are readonly. Similarly to ``, this element allows you to add listeners to events on `document.body`, such as `mouseenter` and `mouseleave`, which don't fire on `window`. It also lets you use [actions](/docs#template-syntax-element-directives-use-action) on the `` element. -As with ``, this element may only appear the top level of your component and must never be inside a block or element. +As with `` and ``, this element may only appear the top level of your component and must never be inside a block or element. ```sv `, this element may only appear the top level of your co This element makes it possible to insert elements into `document.head`. During server-side rendering, `head` content is exposed separately to the main `html` content. -As with `` and ``, this element may only appear at the top level of your component and must never be inside a block or element. +As with ``, `` and ``, this element may only appear at the top level of your component and must never be inside a block or element. ```sv diff --git a/site/content/tutorial/16-special-elements/06-svelte-body/text.md b/site/content/tutorial/16-special-elements/06-svelte-body/text.md deleted file mode 100644 index 95da3ce6084e..000000000000 --- a/site/content/tutorial/16-special-elements/06-svelte-body/text.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -title: ---- - -Similar to ``, the `` element allows you to listen for events that fire on `document.body`. This is useful with the `mouseenter` and `mouseleave` events, which don't fire on `window`. - -Add the `mouseenter` and `mouseleave` handlers to the `` tag: - -```html - -``` \ No newline at end of file diff --git a/site/content/tutorial/16-special-elements/06-svelte-document/app-a/App.svelte b/site/content/tutorial/16-special-elements/06-svelte-document/app-a/App.svelte new file mode 100644 index 000000000000..cea052ec883e --- /dev/null +++ b/site/content/tutorial/16-special-elements/06-svelte-document/app-a/App.svelte @@ -0,0 +1,10 @@ + + + + +

Select this text to fire events

+

Selection: {selection}

diff --git a/site/content/tutorial/16-special-elements/06-svelte-document/app-b/App.svelte b/site/content/tutorial/16-special-elements/06-svelte-document/app-b/App.svelte new file mode 100644 index 000000000000..5dd44aa3890c --- /dev/null +++ b/site/content/tutorial/16-special-elements/06-svelte-document/app-b/App.svelte @@ -0,0 +1,10 @@ + + + + +

Select this text to fire events

+

Selection: {selection}

diff --git a/site/content/tutorial/16-special-elements/06-svelte-document/text.md b/site/content/tutorial/16-special-elements/06-svelte-document/text.md new file mode 100644 index 000000000000..b40c83a6f257 --- /dev/null +++ b/site/content/tutorial/16-special-elements/06-svelte-document/text.md @@ -0,0 +1,13 @@ +--- +title: +--- + +Similar to ``, the `` element allows you to listen for events that fire on `document`. This is useful with events like `selectionchange`, which doesn't fire on `window`. + +Add the `selectionchange` handler to the `` tag: + +```html + +``` + +> Avoid `mouseenter` and `mouseleave` handlers on this element, these events are not fired on `document` in all browsers. Use `` for this instead. diff --git a/site/content/tutorial/16-special-elements/06-svelte-body/app-a/App.svelte b/site/content/tutorial/16-special-elements/07-svelte-body/app-a/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/06-svelte-body/app-a/App.svelte rename to site/content/tutorial/16-special-elements/07-svelte-body/app-a/App.svelte diff --git a/site/content/tutorial/16-special-elements/06-svelte-body/app-b/App.svelte b/site/content/tutorial/16-special-elements/07-svelte-body/app-b/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/06-svelte-body/app-b/App.svelte rename to site/content/tutorial/16-special-elements/07-svelte-body/app-b/App.svelte diff --git a/site/content/tutorial/16-special-elements/07-svelte-body/text.md b/site/content/tutorial/16-special-elements/07-svelte-body/text.md new file mode 100644 index 000000000000..4755281d6149 --- /dev/null +++ b/site/content/tutorial/16-special-elements/07-svelte-body/text.md @@ -0,0 +1,14 @@ +--- +title: +--- + +Similar to `` and ``, the `` element allows you to listen for events that fire on `document.body`. This is useful with the `mouseenter` and `mouseleave` events, which don't fire on `window`. + +Add the `mouseenter` and `mouseleave` handlers to the `` tag: + +```html + +``` \ No newline at end of file diff --git a/site/content/tutorial/16-special-elements/07-svelte-head/app-a/App.svelte b/site/content/tutorial/16-special-elements/08-svelte-head/app-a/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/07-svelte-head/app-a/App.svelte rename to site/content/tutorial/16-special-elements/08-svelte-head/app-a/App.svelte diff --git a/site/content/tutorial/16-special-elements/07-svelte-head/app-b/App.svelte b/site/content/tutorial/16-special-elements/08-svelte-head/app-b/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/07-svelte-head/app-b/App.svelte rename to site/content/tutorial/16-special-elements/08-svelte-head/app-b/App.svelte diff --git a/site/content/tutorial/16-special-elements/07-svelte-head/text.md b/site/content/tutorial/16-special-elements/08-svelte-head/text.md similarity index 100% rename from site/content/tutorial/16-special-elements/07-svelte-head/text.md rename to site/content/tutorial/16-special-elements/08-svelte-head/text.md diff --git a/site/content/tutorial/16-special-elements/08-svelte-options/app-a/App.svelte b/site/content/tutorial/16-special-elements/09-svelte-options/app-a/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/08-svelte-options/app-a/App.svelte rename to site/content/tutorial/16-special-elements/09-svelte-options/app-a/App.svelte diff --git a/site/content/tutorial/16-special-elements/08-svelte-options/app-a/Todo.svelte b/site/content/tutorial/16-special-elements/09-svelte-options/app-a/Todo.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/08-svelte-options/app-a/Todo.svelte rename to site/content/tutorial/16-special-elements/09-svelte-options/app-a/Todo.svelte diff --git a/site/content/tutorial/16-special-elements/08-svelte-options/app-a/flash.js b/site/content/tutorial/16-special-elements/09-svelte-options/app-a/flash.js similarity index 100% rename from site/content/tutorial/16-special-elements/08-svelte-options/app-a/flash.js rename to site/content/tutorial/16-special-elements/09-svelte-options/app-a/flash.js diff --git a/site/content/tutorial/16-special-elements/08-svelte-options/app-b/App.svelte b/site/content/tutorial/16-special-elements/09-svelte-options/app-b/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/08-svelte-options/app-b/App.svelte rename to site/content/tutorial/16-special-elements/09-svelte-options/app-b/App.svelte diff --git a/site/content/tutorial/16-special-elements/08-svelte-options/app-b/Todo.svelte b/site/content/tutorial/16-special-elements/09-svelte-options/app-b/Todo.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/08-svelte-options/app-b/Todo.svelte rename to site/content/tutorial/16-special-elements/09-svelte-options/app-b/Todo.svelte diff --git a/site/content/tutorial/16-special-elements/08-svelte-options/app-b/flash.js b/site/content/tutorial/16-special-elements/09-svelte-options/app-b/flash.js similarity index 100% rename from site/content/tutorial/16-special-elements/08-svelte-options/app-b/flash.js rename to site/content/tutorial/16-special-elements/09-svelte-options/app-b/flash.js diff --git a/site/content/tutorial/16-special-elements/08-svelte-options/text.md b/site/content/tutorial/16-special-elements/09-svelte-options/text.md similarity index 100% rename from site/content/tutorial/16-special-elements/08-svelte-options/text.md rename to site/content/tutorial/16-special-elements/09-svelte-options/text.md diff --git a/site/content/tutorial/16-special-elements/09-svelte-fragment/app-a/App.svelte b/site/content/tutorial/16-special-elements/10-svelte-fragment/app-a/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/09-svelte-fragment/app-a/App.svelte rename to site/content/tutorial/16-special-elements/10-svelte-fragment/app-a/App.svelte diff --git a/site/content/tutorial/16-special-elements/09-svelte-fragment/app-a/Box.svelte b/site/content/tutorial/16-special-elements/10-svelte-fragment/app-a/Box.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/09-svelte-fragment/app-a/Box.svelte rename to site/content/tutorial/16-special-elements/10-svelte-fragment/app-a/Box.svelte diff --git a/site/content/tutorial/16-special-elements/09-svelte-fragment/app-b/App.svelte b/site/content/tutorial/16-special-elements/10-svelte-fragment/app-b/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/09-svelte-fragment/app-b/App.svelte rename to site/content/tutorial/16-special-elements/10-svelte-fragment/app-b/App.svelte diff --git a/site/content/tutorial/16-special-elements/09-svelte-fragment/app-b/Box.svelte b/site/content/tutorial/16-special-elements/10-svelte-fragment/app-b/Box.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/09-svelte-fragment/app-b/Box.svelte rename to site/content/tutorial/16-special-elements/10-svelte-fragment/app-b/Box.svelte diff --git a/site/content/tutorial/16-special-elements/09-svelte-fragment/text.md b/site/content/tutorial/16-special-elements/10-svelte-fragment/text.md similarity index 100% rename from site/content/tutorial/16-special-elements/09-svelte-fragment/text.md rename to site/content/tutorial/16-special-elements/10-svelte-fragment/text.md diff --git a/src/compiler/compile/compiler_warnings.ts b/src/compiler/compile/compiler_warnings.ts index d6b2dbc5aa71..f90720ec599c 100644 --- a/src/compiler/compile/compiler_warnings.ts +++ b/src/compiler/compile/compiler_warnings.ts @@ -213,5 +213,9 @@ export default { invalid_rest_eachblock_binding: (rest_element_name: string) => ({ code: 'invalid-rest-eachblock-binding', message: `...${rest_element_name} operator will create a new object and binding propagation with original object will not work` - }) + }), + avoid_mouse_events_on_document: { + code: 'avoid-mouse-events-on-document', + message: 'Mouse enter/leave events on the document are not supported in all browsers and should be avoided' + } }; diff --git a/src/compiler/compile/nodes/Document.ts b/src/compiler/compile/nodes/Document.ts new file mode 100644 index 000000000000..653ccb627bf1 --- /dev/null +++ b/src/compiler/compile/nodes/Document.ts @@ -0,0 +1,41 @@ +import Node from './shared/Node'; +import EventHandler from './EventHandler'; +import Action from './Action'; +import Component from '../Component'; +import TemplateScope from './shared/TemplateScope'; +import { Element } from '../../interfaces'; +import compiler_warnings from '../compiler_warnings'; + +export default class Document extends Node { + type: 'Document'; + handlers: EventHandler[] = []; + actions: Action[] = []; + + constructor(component: Component, parent: Node, scope: TemplateScope, info: Element) { + super(component, parent, scope, info); + + info.attributes.forEach((node) => { + if (node.type === 'EventHandler') { + this.handlers.push(new EventHandler(component, this, scope, node)); + } else if (node.type === 'Action') { + this.actions.push(new Action(component, this, scope, node)); + } else { + // TODO there shouldn't be anything else here... + } + }); + + this.validate(); + } + + private validate() { + const handlers_map = new Set(); + + this.handlers.forEach(handler => ( + handlers_map.add(handler.name) + )); + + if (handlers_map.has('mouseenter') || handlers_map.has('mouseleave')) { + this.component.warn(this, compiler_warnings.avoid_mouse_events_on_document); + } + } +} diff --git a/src/compiler/compile/nodes/interfaces.ts b/src/compiler/compile/nodes/interfaces.ts index f023cad25c9f..b4740267fdfe 100644 --- a/src/compiler/compile/nodes/interfaces.ts +++ b/src/compiler/compile/nodes/interfaces.ts @@ -12,6 +12,7 @@ import StyleDirective from './StyleDirective'; import Comment from './Comment'; import ConstTag from './ConstTag'; import DebugTag from './DebugTag'; +import Document from './Document'; import EachBlock from './EachBlock'; import Element from './Element'; import ElseBlock from './ElseBlock'; @@ -47,6 +48,7 @@ export type INode = Action | Comment | ConstTag | DebugTag +| Document | EachBlock | Element | ElseBlock diff --git a/src/compiler/compile/nodes/shared/map_children.ts b/src/compiler/compile/nodes/shared/map_children.ts index e6ad1d7f6eec..7338148b635f 100644 --- a/src/compiler/compile/nodes/shared/map_children.ts +++ b/src/compiler/compile/nodes/shared/map_children.ts @@ -3,6 +3,7 @@ import Body from '../Body'; import ConstTag from '../ConstTag'; import Comment from '../Comment'; import EachBlock from '../EachBlock'; +import Document from '../Document'; import Element from '../Element'; import Head from '../Head'; import IfBlock from '../IfBlock'; @@ -28,6 +29,7 @@ function get_constructor(type) { case 'Body': return Body; case 'Comment': return Comment; case 'ConstTag': return ConstTag; + case 'Document': return Document; case 'EachBlock': return EachBlock; case 'Element': return Element; case 'Head': return Head; diff --git a/src/compiler/compile/render_dom/wrappers/Document.ts b/src/compiler/compile/render_dom/wrappers/Document.ts new file mode 100644 index 000000000000..4f7c86c54f91 --- /dev/null +++ b/src/compiler/compile/render_dom/wrappers/Document.ts @@ -0,0 +1,25 @@ +import Block from '../Block'; +import Wrapper from './shared/Wrapper'; +import { x } from 'code-red'; +import Document from '../../nodes/Document'; +import { Identifier } from 'estree'; +import EventHandler from './Element/EventHandler'; +import add_event_handlers from './shared/add_event_handlers'; +import { TemplateNode } from '../../../interfaces'; +import Renderer from '../Renderer'; +import add_actions from './shared/add_actions'; + +export default class DocumentWrapper extends Wrapper { + node: Document; + handlers: EventHandler[]; + + constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) { + super(renderer, block, parent, node); + this.handlers = this.node.handlers.map(handler => new EventHandler(handler, this)); + } + + render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { + add_event_handlers(block, x`@_document`, this.handlers); + add_actions(block, x`@_document`, this.node.actions); + } +} diff --git a/src/compiler/compile/render_dom/wrappers/Fragment.ts b/src/compiler/compile/render_dom/wrappers/Fragment.ts index e2eb51939328..8c633aeb71fd 100644 --- a/src/compiler/compile/render_dom/wrappers/Fragment.ts +++ b/src/compiler/compile/render_dom/wrappers/Fragment.ts @@ -2,6 +2,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/index'; import Head from './Head'; @@ -28,6 +29,7 @@ const wrappers = { Body, Comment: null, DebugTag, + Document, EachBlock, Element, Head, diff --git a/src/compiler/compile/render_ssr/Renderer.ts b/src/compiler/compile/render_ssr/Renderer.ts index 99a2ee3d685f..ac78d032ead8 100644 --- a/src/compiler/compile/render_ssr/Renderer.ts +++ b/src/compiler/compile/render_ssr/Renderer.ts @@ -28,6 +28,7 @@ const handlers: Record = { Body: noop, Comment, DebugTag, + Document: noop, EachBlock, Element, Head, diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts index 402f9ff5e143..afe801b0f10d 100644 --- a/src/compiler/interfaces.ts +++ b/src/compiler/interfaces.ts @@ -63,7 +63,7 @@ interface BaseExpressionDirective extends BaseDirective { } export interface Element extends BaseNode { - type: 'InlineComponent' | 'SlotTemplate' | 'Title' | 'Slot' | 'Element' | 'Head' | 'Options' | 'Window' | 'Body'; + type: 'InlineComponent' | 'SlotTemplate' | 'Title' | 'Slot' | 'Element' | 'Head' | 'Options' | 'Window' | 'Document' | 'Body'; attributes: Array; name: string; } diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts index cbd3213f531b..90adfb8e79dc 100644 --- a/src/compiler/parse/state/tag.ts +++ b/src/compiler/parse/state/tag.ts @@ -19,6 +19,7 @@ const meta_tags = new Map([ ['svelte:head', 'Head'], ['svelte:options', 'Options'], ['svelte:window', 'Window'], + ['svelte:document', 'Document'], ['svelte:body', 'Body'] ]); diff --git a/test/parser/samples/error-svelte-selfdestructive/error.json b/test/parser/samples/error-svelte-selfdestructive/error.json index 68aa43dec9b5..f7cca127409b 100644 --- a/test/parser/samples/error-svelte-selfdestructive/error.json +++ b/test/parser/samples/error-svelte-selfdestructive/error.json @@ -1,6 +1,6 @@ { "code": "invalid-tag-name", - "message": "Valid tag names are svelte:head, svelte:options, svelte:window, svelte:body, svelte:self, svelte:component, svelte:fragment or svelte:element", + "message": "Valid tag names are svelte:head, svelte:options, svelte:window, svelte:document, svelte:body, svelte:self, svelte:component, svelte:fragment or svelte:element", "pos": 10, "start": { "character": 10, diff --git a/test/runtime/samples/action-document/_config.js b/test/runtime/samples/action-document/_config.js new file mode 100644 index 000000000000..d33cdb3fa4c9 --- /dev/null +++ b/test/runtime/samples/action-document/_config.js @@ -0,0 +1,14 @@ +export default { + html: '
', + + async test({ assert, target, window }) { + const visibility = new window.Event('visibilitychange'); + + await window.document.dispatchEvent(visibility); + assert.htmlEqual(target.innerHTML, ` +
+
Perform an Action
+
+ `); + } +}; diff --git a/test/runtime/samples/action-document/main.svelte b/test/runtime/samples/action-document/main.svelte new file mode 100644 index 000000000000..eec4b3fecd88 --- /dev/null +++ b/test/runtime/samples/action-document/main.svelte @@ -0,0 +1,24 @@ + + + +