[feat] allow use:actions on <svelte:body> (#6608)

pull/6619/head
Tan Li Hau 3 years ago committed by GitHub
parent 74a1f29c26
commit ce550adef6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1586,7 +1586,9 @@ All except `scrollX` and `scrollY` are readonly.
--- ---
As with `<svelte:window>`, this element allows you to add listeners to events on `document.body`, such as `mouseenter` and `mouseleave` which don't fire on `window`; and it has to appear at the top level of your component. Similarly to `<svelte:window>`, 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#use_action) on the `<body>` element.
`<svelte:body>` also has to appear at the top level of your component.
```sv ```sv
<svelte:body <svelte:body

@ -1,21 +1,23 @@
import Node from './shared/Node'; import Node from './shared/Node';
import EventHandler from './EventHandler'; import EventHandler from './EventHandler';
import Action from './Action';
import Component from '../Component'; import Component from '../Component';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import { TemplateNode } from '../../interfaces'; import { Element } from '../../interfaces';
export default class Body extends Node { export default class Body extends Node {
type: 'Body'; type: 'Body';
handlers: EventHandler[]; handlers: EventHandler[] = [];
actions: Action[] = [];
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Node, scope: TemplateScope, info: Element) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.handlers = []; info.attributes.forEach((node) => {
info.attributes.forEach((node: Node) => {
if (node.type === 'EventHandler') { if (node.type === 'EventHandler') {
this.handlers.push(new EventHandler(component, this, scope, node)); this.handlers.push(new EventHandler(component, this, scope, node));
} else if (node.type === 'Action') {
this.actions.push(new Action(component, this, scope, node));
} else { } else {
// TODO there shouldn't be anything else here... // TODO there shouldn't be anything else here...
} }

@ -7,6 +7,7 @@ import EventHandler from './Element/EventHandler';
import add_event_handlers from './shared/add_event_handlers'; import add_event_handlers from './shared/add_event_handlers';
import { TemplateNode } from '../../../interfaces'; import { TemplateNode } from '../../../interfaces';
import Renderer from '../Renderer'; import Renderer from '../Renderer';
import add_actions from './shared/add_actions';
export default class BodyWrapper extends Wrapper { export default class BodyWrapper extends Wrapper {
node: Body; node: Body;
@ -19,5 +20,6 @@ export default class BodyWrapper extends Wrapper {
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
add_event_handlers(block, x`@_document.body`, this.handlers); add_event_handlers(block, x`@_document.body`, this.handlers);
add_actions(block, x`@_document.body`, this.node.actions);
} }
} }

@ -1,17 +1,18 @@
import { b, x } from 'code-red'; import { b, x } from 'code-red';
import Block from '../../Block'; import Block from '../../Block';
import Action from '../../../nodes/Action'; import Action from '../../../nodes/Action';
import { Expression } from 'estree';
import is_contextual from '../../../nodes/shared/is_contextual'; import is_contextual from '../../../nodes/shared/is_contextual';
export default function add_actions( export default function add_actions(
block: Block, block: Block,
target: string, target: string | Expression,
actions: Action[] actions: Action[]
) { ) {
actions.forEach(action => add_action(block, target, action)); actions.forEach(action => add_action(block, target, action));
} }
export function add_action(block: Block, target: string, action: Action) { export function add_action(block: Block, target: string | Expression, action: Action) {
const { expression, template_scope } = action; const { expression, template_scope } = action;
let snippet; let snippet;
let dependencies; let dependencies;

@ -46,6 +46,23 @@ interface BaseDirective extends BaseNode {
modifiers: string[]; modifiers: string[];
} }
export interface Element extends BaseNode {
type: 'InlineComponent' | 'SlotTemplate' | 'Title' | 'Slot' | 'Element' | 'Head' | 'Options' | 'Window' | 'Body';
attributes: Array<BaseDirective | Attribute | SpreadAttribute>;
name: string;
}
export interface Attribute extends BaseNode {
type: 'Attribute';
name: string;
value: any[];
}
export interface SpreadAttribute extends BaseNode {
type: 'Spread';
expression: Node;
}
export interface Transition extends BaseDirective { export interface Transition extends BaseDirective {
type: 'Transition'; type: 'Transition';
intro: boolean; intro: boolean;
@ -57,6 +74,9 @@ export type Directive = BaseDirective | Transition;
export type TemplateNode = Text export type TemplateNode = Text
| MustacheTag | MustacheTag
| BaseNode | BaseNode
| Element
| Attribute
| SpreadAttribute
| Directive | Directive
| Transition | Transition
| Comment; | Comment;

@ -0,0 +1,18 @@
export default {
html: '<div></div>',
async test({ assert, target, window }) {
const enter = new window.MouseEvent('mouseenter');
const leave = new window.MouseEvent('mouseleave');
await window.document.body.dispatchEvent(enter);
assert.htmlEqual(target.innerHTML, `
<div>
<div class="tooltip">Perform an Action</div>
</div>
`);
await window.document.body.dispatchEvent(leave);
assert.htmlEqual(target.innerHTML, '<div></div>');
}
};

@ -0,0 +1,32 @@
<script>
let container;
function tooltip(node, text) {
let tooltip = null;
function onMouseEnter() {
tooltip = document.createElement('div');
tooltip.classList.add('tooltip');
tooltip.textContent = text;
container.appendChild(tooltip);
}
function onMouseLeave() {
if (!tooltip) return;
tooltip.remove();
tooltip = null;
}
node.addEventListener('mouseenter', onMouseEnter);
node.addEventListener('mouseleave', onMouseLeave);
return {
destroy() {
node.removeEventListener('mouseenter', onMouseEnter);
node.removeEventListener('mouseleave', onMouseLeave);
}
}
}
</script>
<svelte:body use:tooltip="{'Perform an Action'}" />
<div bind:this={container} />
Loading…
Cancel
Save