Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: class directive on <svelte:body> #6928

Closed
wants to merge 9 commits into from
4 changes: 4 additions & 0 deletions src/compiler/compile/nodes/Body.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Node from './shared/Node';
import EventHandler from './EventHandler';
import Action from './Action';
import Class from './Class';
import Component from '../Component';
import TemplateScope from './shared/TemplateScope';
import { Element } from '../../interfaces';
Expand All @@ -9,6 +10,7 @@ export default class Body extends Node {
type: 'Body';
handlers: EventHandler[] = [];
actions: Action[] = [];
classes: Class[] = [];

constructor(component: Component, parent: Node, scope: TemplateScope, info: Element) {
super(component, parent, scope, info);
Expand All @@ -18,6 +20,8 @@ export default class Body extends 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 if (node.type === 'Class') {
this.classes.push(new Class(component, this, scope, node));
} else {
// TODO there shouldn't be anything else here...
}
Expand Down
45 changes: 43 additions & 2 deletions src/compiler/compile/render_dom/wrappers/Body.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,66 @@
import Block from '../Block';
import Wrapper from './shared/Wrapper';
import { x } from 'code-red';
import { b, x } from 'code-red';
import Body from '../../nodes/Body';
import { Identifier } from 'estree';
import { Node, 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';
import is_dynamic from './shared/is_dynamic';

export default class BodyWrapper extends Wrapper {
node: Body;
handlers: EventHandler[];
class_dependencies: string[];

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));
this.class_dependencies = [];
}

add_classes(block: Block) {
this.node.classes.forEach(class_directive => {
const { expression, name } = class_directive;
let snippet: Node | string;
let dependencies: Set<string>;
if (expression) {
snippet = expression.manipulate(block);
dependencies = expression.dependencies;
} else {
snippet = name;
dependencies = new Set([name]);
}
const updater = b`@toggle_class(@_document.body, "${name}", ${snippet});`;

block.chunks.hydrate.push(updater);

if ((dependencies && dependencies.size > -1) || this.class_dependencies.length) {
const all_dependencies = this.class_dependencies.concat(...dependencies);
const condition = block.renderer.dirty(all_dependencies);

// If all of the dependencies are non-dynamic (don't get updated) then there is no reason
// to add an updater for this.
const any_dynamic_dependencies = all_dependencies.some((dep) => {
const variable = this.renderer.component.var_lookup.get(dep);
return !variable || is_dynamic(variable);
});
if (any_dynamic_dependencies) {
block.chunks.update.push(b`
if (${condition}) {
${updater}
}
`);
}
}
});
}

render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
add_event_handlers(block, x`@_document.body`, this.handlers);
add_actions(block, x`@_document.body`, this.node.actions);
this.add_classes(block);
}
}
3 changes: 3 additions & 0 deletions test/runtime/samples/class-body-boolean/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
html: '<body class="one"></body>'
};
1 change: 1 addition & 0 deletions test/runtime/samples/class-body-boolean/main.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<svelte:body class:one={true} />
16 changes: 16 additions & 0 deletions test/runtime/samples/class-body-shortcut/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export default {
props: {
foo: true,
bar: true
},

html: '<body class="foo bar"></body>',

test({ assert, component, target }) {
component.foo = false;

assert.htmlEqual(target.innerHTML, `
<body class="bar"></body>
`);
}
};
7 changes: 7 additions & 0 deletions test/runtime/samples/class-body-shortcut/main.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script>
export let foo;
export let bar;
export let unused;
</script>

<svelte:body class:foo class:bar class:unused />