Skip to content

Commit 43a083b

Browse files
committed
Add SVG support & demo
1 parent ffd9e46 commit 43a083b

File tree

4 files changed

+218
-6
lines changed

4 files changed

+218
-6
lines changed

demo/clock.html

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
<!--
3+
@license
4+
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
5+
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
6+
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
7+
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
8+
Code distributed by Google as part of the polymer project is also
9+
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
10+
-->
11+
<!doctype html>
12+
<html>
13+
<head>
14+
<script type="module" src="./clock.js"></script>
15+
</head>
16+
<body>
17+
<lit-clock></lit-clock>
18+
</body>
19+
</html>

demo/clock.js

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/**
2+
* @license
3+
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
4+
* This code may only be used under the BSD style license found at
5+
* http://polymer.github.io/LICENSE.txt
6+
* The complete set of authors may be found at
7+
* http://polymer.github.io/AUTHORS.txt
8+
* The complete set of contributors may be found at
9+
* http://polymer.github.io/CONTRIBUTORS.txt
10+
* Code distributed by Google as part of the polymer project is also
11+
* subject to an additional IP rights grant found at
12+
* http://polymer.github.io/PATENTS.txt
13+
*/
14+
15+
16+
import {html, render, svg} from '../lit-html.js';
17+
18+
/**
19+
* Adapted from the Ractive.js clock example: http://www.ractivejs.org/examples/clock/
20+
*/
21+
export class LitClock extends HTMLElement {
22+
23+
get date() { return this._date; }
24+
set date(v) { this._date = v; this.invalidate(); }
25+
26+
constructor() {
27+
super();
28+
this.attachShadow({mode: 'open'});
29+
setInterval(() => {
30+
this.date = new Date();
31+
}, 1000);
32+
}
33+
34+
render() {
35+
return html`
36+
<style>
37+
:host {
38+
display: block;
39+
}
40+
.square {
41+
position: relative;
42+
width: 100%;
43+
height: 0;
44+
padding-bottom: 100%;
45+
}
46+
47+
svg {
48+
position: absolute;
49+
width: 100%;
50+
height: 100%;
51+
}
52+
53+
.clock-face {
54+
stroke: #333;
55+
fill: white;
56+
}
57+
58+
.minor {
59+
stroke: #999;
60+
stroke-width: 0.5;
61+
}
62+
63+
.major {
64+
stroke: #333;
65+
stroke-width: 1;
66+
}
67+
68+
.hour {
69+
stroke: #333;
70+
}
71+
72+
.minute {
73+
stroke: #666;
74+
}
75+
76+
.second, .second-counterweight {
77+
stroke: rgb(180,0,0);
78+
}
79+
80+
.second-counterweight {
81+
stroke-width: 3;
82+
}
83+
</style>
84+
<div class='square'> <!-- so the SVG keeps its aspect ratio -->
85+
86+
<svg viewBox='0 0 100 100'>
87+
88+
<!-- first create a group and move it to 50,50 so
89+
all co-ords are relative to the center -->
90+
<g transform='translate(50,50)'>
91+
<circle class='clock-face' r='48'/>
92+
${minuteTicks}
93+
${hourTicks}
94+
95+
<!-- hour hand -->
96+
<line class='hour' y1='2' y2='-20'
97+
transform='rotate(${ 30 * this.date.getHours() + this.date.getMinutes() / 2 })'/>
98+
99+
<!-- minute hand -->
100+
<line class='minute' y1='4' y2='-30'
101+
transform='rotate(${ 6 * this.date.getMinutes() + this.date.getSeconds() / 10 })'/>
102+
103+
<!-- second hand -->
104+
<g transform='rotate(${ 6 * this.date.getSeconds() })'>
105+
<line class='second' y1='10' y2='-38'/>
106+
<line class='second-counterweight' y1='10' y2='2'/>
107+
</g>
108+
</g>
109+
</svg>
110+
</div>
111+
`;
112+
}
113+
114+
invalidate() {
115+
if (!this.needsRender) {
116+
this.needsRender = true;
117+
Promise.resolve().then(() => {
118+
this.needsRender = false;
119+
render(this.render(), this.shadowRoot);
120+
});
121+
}
122+
}
123+
}
124+
customElements.define('lit-clock', LitClock);
125+
126+
const minuteTicks = (() => {
127+
const lines = [];
128+
for (let i = 0; i < 60; i++) {
129+
lines.push(svg`
130+
<line
131+
class='minor'
132+
y1='42'
133+
y2='45'
134+
transform='rotate(${360 * i / 60})'/>
135+
`);
136+
}
137+
return lines;
138+
})();
139+
140+
const hourTicks = (() => {
141+
const lines = [];
142+
for (let i = 0; i < 12; i++) {
143+
lines.push(svg`
144+
<line
145+
class='major'
146+
y1='32'
147+
y2='45'
148+
transform='rotate(${360 * i / 12})'/>
149+
`);
150+
}
151+
return lines;
152+
})();

src/lit-html.ts

+37-5
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,30 @@
1616
// calls to a tag for the same literal, so we can cache work done per literal
1717
// in a Map.
1818
const templates = new Map<TemplateStringsArray, Template>();
19+
const svgTemplates = new Map<TemplateStringsArray, Template>();
1920

2021
/**
2122
* Interprets a template literal as an HTML template that can efficiently
2223
* render to and update a container.
2324
*/
24-
export function html(
25-
strings: TemplateStringsArray, ...values: any[]): TemplateResult {
25+
export const html = (strings: TemplateStringsArray, ...values: any[]) =>
26+
litTag(strings, values, templates, false);
27+
28+
/**
29+
* Interprets a template literal as an SVG template that can efficiently
30+
* render to and update a container.
31+
*/
32+
export const svg = (strings: TemplateStringsArray, ...values: any[]) =>
33+
litTag(strings, values, svgTemplates, true);
34+
35+
function litTag(
36+
strings: TemplateStringsArray,
37+
values: any[],
38+
templates: Map<TemplateStringsArray, Template>,
39+
isSvg: boolean): TemplateResult {
2640
let template = templates.get(strings);
2741
if (template === undefined) {
28-
template = new Template(strings);
42+
template = new Template(strings, isSvg);
2943
templates.set(strings, template);
3044
}
3145
return new TemplateResult(template, values);
@@ -110,10 +124,12 @@ export class TemplatePart {
110124
export class Template {
111125
parts: TemplatePart[] = [];
112126
element: HTMLTemplateElement;
127+
svg: boolean;
113128

114-
constructor(strings: TemplateStringsArray) {
129+
constructor(strings: TemplateStringsArray, svg: boolean = false) {
130+
this.svg = svg;
115131
this.element = document.createElement('template');
116-
this.element.innerHTML = strings.join(exprMarker);
132+
this.element.innerHTML = this._getHtml(strings, svg);
117133
const walker = document.createTreeWalker(
118134
this.element.content, 5 /* elements & text */);
119135
let index = -1;
@@ -178,6 +194,14 @@ export class Template {
178194
n.parentNode!.removeChild(n);
179195
}
180196
}
197+
198+
/**
199+
* Returns a string of HTML used to create a <template> element.
200+
*/
201+
private _getHtml(strings: TemplateStringsArray, svg?: boolean): string {
202+
const html = strings.join(exprMarker);
203+
return svg ? `<svg>${html}</svg>` : html;
204+
}
181205
}
182206

183207
export const getValue = (part: Part, value: any) => {
@@ -467,6 +491,14 @@ export class TemplateInstance {
467491
}
468492
}
469493
}
494+
if (this.template.svg) {
495+
const svgElement = fragment.firstChild!;
496+
fragment.removeChild(svgElement);
497+
const nodes = svgElement.childNodes;
498+
for (let i = 0; i < nodes.length; i++) {
499+
fragment.appendChild(nodes.item(i));
500+
}
501+
}
470502
return fragment;
471503
}
472504
}

src/test/lit-html_test.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
/// <reference path="../../node_modules/@types/mocha/index.d.ts" />
1616
/// <reference path="../../node_modules/@types/chai/index.d.ts" />
1717

18-
import {AttributePart, defaultPartCallback, html, NodePart, Part, render, TemplateInstance, TemplatePart, TemplateResult} from '../lit-html.js';
18+
import {AttributePart, defaultPartCallback, html, NodePart, Part, render, svg, TemplateInstance, TemplatePart, TemplateResult} from '../lit-html.js';
1919

2020
const assert = chai.assert;
2121

@@ -322,6 +322,15 @@ suite('lit-html', () => {
322322
<p>qux</p></div>`);
323323
});
324324

325+
test('renders SVG', () => {
326+
const container = document.createElement('svg');
327+
const t = svg`<line y1="1" y2="1"/>`;
328+
render(t, container);
329+
const line = container.firstElementChild!;
330+
assert.equal(line.tagName, 'line');
331+
assert.equal(line.namespaceURI, 'http://www.w3.org/2000/svg');
332+
});
333+
325334
});
326335

327336
suite('update', () => {

0 commit comments

Comments
 (0)