Skip to content

Commit

Permalink
compile: support classes on inline components
Browse files Browse the repository at this point in the history
  • Loading branch information
nikku committed May 28, 2019
1 parent 242c3d3 commit 5214c75
Show file tree
Hide file tree
Showing 27 changed files with 510 additions and 8 deletions.
13 changes: 9 additions & 4 deletions src/compile/css/Stylesheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ import MagicString from 'magic-string';
import { walk } from 'estree-walker';
import Selector from './Selector';
import Element from '../nodes/Element';
import InlineComponent from '../nodes/InlineComponent';
import { Node, Ast } from '../../interfaces';
import Component from '../Component';

type ClassTarget = Element
| InlineComponent;


function remove_css_prefix(name: string): string {
return name.replace(/^-((webkit)|(moz)|(o)|(ms))-/, '');
}
Expand Down Expand Up @@ -33,7 +38,7 @@ class Rule {
this.declarations = node.block.children.map((node: Node) => new Declaration(node));
}

apply(node: Element, stack: Element[]) {
apply(node: ClassTarget, stack: ClassTarget[]) {
this.selectors.forEach(selector => selector.apply(node, stack)); // TODO move the logic in here?
}

Expand Down Expand Up @@ -147,7 +152,7 @@ class Atrule {
this.children = [];
}

apply(node: Element, stack: Element[]) {
apply(node: ClassTarget, stack: ClassTarget[]) {
if (this.node.name === 'media' || this.node.name === 'supports') {
this.children.forEach(child => {
child.apply(node, stack);
Expand Down Expand Up @@ -323,10 +328,10 @@ export default class Stylesheet {
}
}

apply(node: Element) {
apply(node: ClassTarget) {
if (!this.has_styles) return;

const stack: Element[] = [];
const stack: ClassTarget[] = [];
let parent: Node = node;
while (parent = parent.parent) {
if (parent.type === 'Element') stack.unshift(parent as Element);
Expand Down
35 changes: 31 additions & 4 deletions src/compile/nodes/InlineComponent.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import Node from './shared/Node';
import Attribute from './Attribute';
import Class from './Class';
import Text from './Text';
import map_children from './shared/map_children';
import Binding from './Binding';
import EventHandler from './EventHandler';
Expand All @@ -15,6 +17,7 @@ export default class InlineComponent extends Node {
expression: Expression;
attributes: Attribute[] = [];
bindings: Binding[] = [];
classes: Class[] = [];
handlers: EventHandler[] = [];
lets: Let[] = [];
children: INode[];
Expand Down Expand Up @@ -60,10 +63,8 @@ export default class InlineComponent extends Node {
break;

case 'Class':
component.error(node, {
code: `invalid-class`,
message: `Classes can only be applied to DOM elements, not components`
});
this.classes.push(new Class(component, this, scope, node));
break;

case 'EventHandler':
this.handlers.push(new EventHandler(component, this, scope, node));
Expand Down Expand Up @@ -99,5 +100,31 @@ export default class InlineComponent extends Node {
}

this.children = map_children(component, this, this.scope, info.children);

component.stylesheet.apply(this);
}

add_css_class(class_name = this.component.stylesheet.id) {
const class_attribute = this.attributes.find(a => a.name === 'class');
if (class_attribute && !class_attribute.is_true) {
if (class_attribute.chunks.length === 1 && class_attribute.chunks[0].type === 'Text') {
(class_attribute.chunks[0] as Text).data += ` ${class_name}`;
} else {
(<Node[]>class_attribute.chunks).push(
new Text(this.component, this, this.scope, {
type: 'Text',
data: ` ${class_name}`
})
);
}
} else {
this.attributes.push(
new Attribute(this.component, this, this.scope, {
type: 'Attribute',
name: 'class',
value: [{ type: 'Text', data: class_name }]
})
);
}
}
}
1 change: 1 addition & 0 deletions test/css/samples/component-class-dynamic/expected.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.foo.svelte-xyz{color:red}
15 changes: 15 additions & 0 deletions test/css/samples/component-class-dynamic/input.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script>
const Nested = window.Nested;

export let clazz = '';
</script>

<div class="foo">
<Nested class={ clazz } />
</div>

<style>
.foo {
color: red;
}
</style>
1 change: 1 addition & 0 deletions test/css/samples/component-class-mixed-attrs/expected.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.custom-class.svelte-xyz{color:red}
13 changes: 13 additions & 0 deletions test/css/samples/component-class-mixed-attrs/input.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<script>
const Nested = window.Nested;

export let prop;
</script>

<Nested class="custom-class" prop={ prop } />

<style>
.custom-class {
color: red;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.foo.svelte-xyz{color:red}
13 changes: 13 additions & 0 deletions test/css/samples/component-class-no-definition/input.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<script>
const Nested = window.Nested;
</script>

<div class="foo">
<Nested class="no-definition" />
</div>

<style>
.foo {
color: red;
}
</style>
25 changes: 25 additions & 0 deletions test/css/samples/component-class-unused-selector/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export default {
warnings: [{
filename: "SvelteComponent.svelte",
code: `css-unused-selector`,
message: "Unused CSS selector",
start: {
line: 12,
column: 1,
character: 110
},
end: {
line: 12,
column: 5,
character: 114
},
pos: 110,
frame: `
10: }
11:
12: .bar {
^
13: color: blue;
14: }`
}]
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.foo.svelte-xyz{color:red}
15 changes: 15 additions & 0 deletions test/css/samples/component-class-unused-selector/input.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script>
const Nested = window.Nested;
</script>

<Nested class="foo" />

<style>
.foo {
color: red;
}

.bar {
color: blue;
}
</style>
1 change: 1 addition & 0 deletions test/css/samples/component-class/expected.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.custom-class.svelte-xyz{color:red}
11 changes: 11 additions & 0 deletions test/css/samples/component-class/input.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script>
const Nested = window.Nested;
</script>

<Nested class="custom-class" />

<style>
.custom-class {
color: red;
}
</style>
1 change: 1 addition & 0 deletions test/css/samples/component-classes/expected.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.custom.svelte-xyz{color:red}.custom2.svelte-xyz{background:yellow}
17 changes: 17 additions & 0 deletions test/css/samples/component-classes/input.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script>
const Nested = window.Nested;

export let clazz = 'foo';
</script>

<Nested class="custom { clazz } custom2" />

<style>
.custom {
color: red;
}

.custom2 {
background: yellow;
}
</style>
61 changes: 61 additions & 0 deletions test/js/samples/component-class-mixed-attrs/expected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* generated by Svelte vX.Y.Z */
import {
SvelteComponent,
init,
mount_component,
noop,
safe_not_equal
} from "svelte/internal";

function create_fragment(ctx) {
var current;

var nested = new ctx.Nested({ props: { class: "custom" } });
nested.$on("click", ctx.handleClick);

return {
c() {
nested.$$.fragment.c();
},

m(target, anchor) {
mount_component(nested, target, anchor);
current = true;
},

p: noop,

i(local) {
if (current) return;
nested.$$.fragment.i(local);

current = true;
},

o(local) {
nested.$$.fragment.o(local);
current = false;
},

d(detaching) {
nested.$destroy(detaching);
}
};
}

function instance($$self) {
const Nested = window.Nested;

let handleClick = () => {};

return { Nested, handleClick };
}

class Component extends SvelteComponent {
constructor(options) {
super();
init(this, options, instance, create_fragment, safe_not_equal, []);
}
}

export default Component;
7 changes: 7 additions & 0 deletions test/js/samples/component-class-mixed-attrs/input.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script>
const Nested = window.Nested;

let handleClick = () => {};
</script>

<Nested class="custom" on:click={ handleClick } />
77 changes: 77 additions & 0 deletions test/js/samples/component-class-no-definition/expected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/* generated by Svelte vX.Y.Z */
import {
SvelteComponent,
append,
detach,
element,
init,
insert,
mount_component,
noop,
safe_not_equal
} from "svelte/internal";

function add_css() {
var style = element("style");
style.id = 'svelte-sg04hs-style';
style.textContent = ".foo.svelte-sg04hs{color:red}";
append(document.head, style);
}

function create_fragment(ctx) {
var div, current;

var nested = new ctx.Nested({ props: { class: "no-definition" } });

return {
c() {
div = element("div");
nested.$$.fragment.c();
div.className = "foo svelte-sg04hs";
},

m(target, anchor) {
insert(target, div, anchor);
mount_component(nested, div, null);
current = true;
},

p: noop,

i(local) {
if (current) return;
nested.$$.fragment.i(local);

current = true;
},

o(local) {
nested.$$.fragment.o(local);
current = false;
},

d(detaching) {
if (detaching) {
detach(div);
}

nested.$destroy();
}
};
}

function instance($$self) {
const Nested = window.Nested;

return { Nested };
}

class Component extends SvelteComponent {
constructor(options) {
super();
if (!document.getElementById("svelte-sg04hs-style")) add_css();
init(this, options, instance, create_fragment, safe_not_equal, []);
}
}

export default Component;
Loading

0 comments on commit 5214c75

Please sign in to comment.