diff --git a/README.md b/README.md
index c9c2cb5..6068351 100644
--- a/README.md
+++ b/README.md
@@ -37,6 +37,7 @@ resources:
| `fullscreen` | boolean | optional| true | If false it will remove the pop-up wrapper which makes it fullscreen |
| `stepSize` | number | optional| 1 | If your climate gives a target_temp_step in the attributes this will be used, fallback is 1 if you don't set in in your configuration. |
| `settings` | boolean | optional | false | When it will add an settings button that displays the more-info content see settings example for my light popup for more options/information [here]: https://github.com/DBuit/light-popup-card#settings |
+| `settingsPosition` | string | optional | `bottom` | set position of the settings button options: `top` or `bottom`. |
Example configuration in lovelace-ui.yaml
diff --git a/dist/thermostat-popup-card.js b/dist/thermostat-popup-card.js
index 334253f..df06eeb 100644
--- a/dist/thermostat-popup-card.js
+++ b/dist/thermostat-popup-card.js
@@ -11,7 +11,57 @@
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/
-const t=new WeakMap,e=e=>"function"==typeof e&&t.has(e),s=void 0!==window.customElements&&void 0!==window.customElements.polyfillWrapFlushCallback,i=(t,e,s=null,i=null)=>{for(;e!==s;){const s=e.nextSibling;t.insertBefore(e,i),e=s}},n=(t,e,s=null)=>{for(;e!==s;){const s=e.nextSibling;t.removeChild(e),e=s}},r={},o={},a=`{{lit-${String(Math.random()).slice(2)}}}`,l=`\x3c!--${a}--\x3e`,c=new RegExp(`${a}|${l}`),h="$lit$";class d{constructor(t,e){this.parts=[],this.element=e;const s=[],i=[],n=document.createTreeWalker(e.content,133,null,!1);let r=0,o=-1,l=0;const{strings:d,values:{length:p}}=t;for(;l
0;){const e=d[l],s=g.exec(e)[2],i=s.toLowerCase()+h,n=t.getAttribute(i);t.removeAttribute(i);const r=n.split(c);this.parts.push({type:"attribute",index:o,name:s,strings:r}),l+=r.length-1}}"TEMPLATE"===t.tagName&&(i.push(t),n.currentNode=t.content)}else if(3===t.nodeType){const e=t.data;if(e.indexOf(a)>=0){const i=t.parentNode,n=e.split(c),r=n.length-1;for(let e=0;e{const s=t.length-e.length;return s>=0&&t.slice(s)===e},p=t=>-1!==t.index,m=()=>document.createComment(""),g=/([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;
+const directives = new WeakMap();
+/**
+ * Brands a function as a directive factory function so that lit-html will call
+ * the function during template rendering, rather than passing as a value.
+ *
+ * A _directive_ is a function that takes a Part as an argument. It has the
+ * signature: `(part: Part) => void`.
+ *
+ * A directive _factory_ is a function that takes arguments for data and
+ * configuration and returns a directive. Users of directive usually refer to
+ * the directive factory as the directive. For example, "The repeat directive".
+ *
+ * Usually a template author will invoke a directive factory in their template
+ * with relevant arguments, which will then return a directive function.
+ *
+ * Here's an example of using the `repeat()` directive factory that takes an
+ * array and a function to render an item:
+ *
+ * ```js
+ * html`
<${repeat(items, (item) => html`
${item}
`)}
`
+ * ```
+ *
+ * When `repeat` is invoked, it returns a directive function that closes over
+ * `items` and the template function. When the outer template is rendered, the
+ * return directive function is called with the Part for the expression.
+ * `repeat` then performs it's custom logic to render multiple items.
+ *
+ * @param f The directive factory function. Must be a function that returns a
+ * function of the signature `(part: Part) => void`. The returned function will
+ * be called with the part object.
+ *
+ * @example
+ *
+ * import {directive, html} from 'lit-html';
+ *
+ * const immutable = directive((v) => (part) => {
+ * if (part.value !== v) {
+ * part.setValue(v)
+ * }
+ * });
+ */
+const directive = (f) => ((...args) => {
+ const d = f(...args);
+ directives.set(d, true);
+ return d;
+});
+const isDirective = (o) => {
+ return typeof o === 'function' && directives.has(o);
+};
+//# sourceMappingURL=directive.js.map
+
/**
* @license
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
@@ -25,7 +75,61 @@ const t=new WeakMap,e=e=>"function"==typeof e&&t.has(e),s=void 0!==window.custom
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/
-class f{constructor(t,e,s){this.__parts=[],this.template=t,this.processor=e,this.options=s}update(t){let e=0;for(const s of this.__parts)void 0!==s&&s.setValue(t[e]),e++;for(const t of this.__parts)void 0!==t&&t.commit()}_clone(){const t=s?this.template.element.content.cloneNode(!0):document.importNode(this.template.element.content,!0),e=[],i=this.template.parts,n=document.createTreeWalker(t,133,null,!1);let r,o=0,a=0,l=n.nextNode();for(;o {
+ while (start !== end) {
+ const n = start.nextSibling;
+ container.insertBefore(start, before);
+ start = n;
+ }
+};
+/**
+ * Removes nodes, starting from `start` (inclusive) to `end` (exclusive), from
+ * `container`.
+ */
+const removeNodes = (container, start, end = null) => {
+ while (start !== end) {
+ const n = start.nextSibling;
+ container.removeChild(start);
+ start = n;
+ }
+};
+//# sourceMappingURL=dom.js.map
+
+/**
+ * @license
+ * Copyright (c) 2018 The Polymer Project Authors. All rights reserved.
+ * This code may only be used under the BSD style license found at
+ * http://polymer.github.io/LICENSE.txt
+ * The complete set of authors may be found at
+ * http://polymer.github.io/AUTHORS.txt
+ * The complete set of contributors may be found at
+ * http://polymer.github.io/CONTRIBUTORS.txt
+ * Code distributed by Google as part of the polymer project is also
+ * subject to an additional IP rights grant found at
+ * http://polymer.github.io/PATENTS.txt
+ */
+/**
+ * A sentinel value that signals that a value was handled by a directive and
+ * should not be written to the DOM.
+ */
+const noChange = {};
+/**
+ * A sentinel value that signals a NodePart to fully clear its content.
+ */
+const nothing = {};
+//# sourceMappingURL=part.js.map
+
/**
* @license
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
@@ -38,7 +142,208 @@ class f{constructor(t,e,s){this.__parts=[],this.template=t,this.processor=e,this
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
- */const _=` ${a} `;class y{constructor(t,e,s,i){this.strings=t,this.values=e,this.type=s,this.processor=i}getHTML(){const t=this.strings.length-1;let e="",s=!1;for(let i=0;i-1||s)&&-1===t.indexOf("--\x3e",n+1);const r=g.exec(t);e+=null===r?t+(s?_:l):t.substr(0,r.index)+r[1]+r[2]+h+r[3]+a}return e+=this.strings[t],e}getTemplateElement(){const t=document.createElement("template");return t.innerHTML=this.getHTML(),t}}class v extends y{getHTML(){return``}getTemplateElement(){const t=super.getTemplateElement(),e=t.content,s=e.firstChild;return e.removeChild(s),i(e,s.firstChild),t}}
+ */
+/**
+ * An expression marker with embedded unique key to avoid collision with
+ * possible text in templates.
+ */
+const marker = `{{lit-${String(Math.random()).slice(2)}}}`;
+/**
+ * An expression marker used text-positions, multi-binding attributes, and
+ * attributes with markup-like text values.
+ */
+const nodeMarker = ``;
+const markerRegex = new RegExp(`${marker}|${nodeMarker}`);
+/**
+ * Suffix appended to all bound attribute names.
+ */
+const boundAttributeSuffix = '$lit$';
+/**
+ * An updateable Template that tracks the location of dynamic parts.
+ */
+class Template {
+ constructor(result, element) {
+ this.parts = [];
+ this.element = element;
+ const nodesToRemove = [];
+ const stack = [];
+ // Edge needs all 4 parameters present; IE11 needs 3rd parameter to be null
+ const walker = document.createTreeWalker(element.content, 133 /* NodeFilter.SHOW_{ELEMENT|COMMENT|TEXT} */, null, false);
+ // Keeps track of the last index associated with a part. We try to delete
+ // unnecessary nodes, but we never want to associate two different parts
+ // to the same index. They must have a constant node between.
+ let lastPartIndex = 0;
+ let index = -1;
+ let partIndex = 0;
+ const { strings, values: { length } } = result;
+ while (partIndex < length) {
+ const node = walker.nextNode();
+ if (node === null) {
+ // We've exhausted the content inside a nested template element.
+ // Because we still have parts (the outer for-loop), we know:
+ // - There is a template in the stack
+ // - The walker will find a nextNode outside the template
+ walker.currentNode = stack.pop();
+ continue;
+ }
+ index++;
+ if (node.nodeType === 1 /* Node.ELEMENT_NODE */) {
+ if (node.hasAttributes()) {
+ const attributes = node.attributes;
+ const { length } = attributes;
+ // Per
+ // https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap,
+ // attributes are not guaranteed to be returned in document order.
+ // In particular, Edge/IE can return them out of order, so we cannot
+ // assume a correspondence between part index and attribute index.
+ let count = 0;
+ for (let i = 0; i < length; i++) {
+ if (endsWith(attributes[i].name, boundAttributeSuffix)) {
+ count++;
+ }
+ }
+ while (count-- > 0) {
+ // Get the template literal section leading up to the first
+ // expression in this attribute
+ const stringForPart = strings[partIndex];
+ // Find the attribute name
+ const name = lastAttributeNameRegex.exec(stringForPart)[2];
+ // Find the corresponding attribute
+ // All bound attributes have had a suffix added in
+ // TemplateResult#getHTML to opt out of special attribute
+ // handling. To look up the attribute value we also need to add
+ // the suffix.
+ const attributeLookupName = name.toLowerCase() + boundAttributeSuffix;
+ const attributeValue = node.getAttribute(attributeLookupName);
+ node.removeAttribute(attributeLookupName);
+ const statics = attributeValue.split(markerRegex);
+ this.parts.push({ type: 'attribute', index, name, strings: statics });
+ partIndex += statics.length - 1;
+ }
+ }
+ if (node.tagName === 'TEMPLATE') {
+ stack.push(node);
+ walker.currentNode = node.content;
+ }
+ }
+ else if (node.nodeType === 3 /* Node.TEXT_NODE */) {
+ const data = node.data;
+ if (data.indexOf(marker) >= 0) {
+ const parent = node.parentNode;
+ const strings = data.split(markerRegex);
+ const lastIndex = strings.length - 1;
+ // Generate a new text node for each literal section
+ // These nodes are also used as the markers for node parts
+ for (let i = 0; i < lastIndex; i++) {
+ let insert;
+ let s = strings[i];
+ if (s === '') {
+ insert = createMarker();
+ }
+ else {
+ const match = lastAttributeNameRegex.exec(s);
+ if (match !== null && endsWith(match[2], boundAttributeSuffix)) {
+ s = s.slice(0, match.index) + match[1] +
+ match[2].slice(0, -boundAttributeSuffix.length) + match[3];
+ }
+ insert = document.createTextNode(s);
+ }
+ parent.insertBefore(insert, node);
+ this.parts.push({ type: 'node', index: ++index });
+ }
+ // If there's no text, we must insert a comment to mark our place.
+ // Else, we can trust it will stick around after cloning.
+ if (strings[lastIndex] === '') {
+ parent.insertBefore(createMarker(), node);
+ nodesToRemove.push(node);
+ }
+ else {
+ node.data = strings[lastIndex];
+ }
+ // We have a part for each match found
+ partIndex += lastIndex;
+ }
+ }
+ else if (node.nodeType === 8 /* Node.COMMENT_NODE */) {
+ if (node.data === marker) {
+ const parent = node.parentNode;
+ // Add a new marker node to be the startNode of the Part if any of
+ // the following are true:
+ // * We don't have a previousSibling
+ // * The previousSibling is already the start of a previous part
+ if (node.previousSibling === null || index === lastPartIndex) {
+ index++;
+ parent.insertBefore(createMarker(), node);
+ }
+ lastPartIndex = index;
+ this.parts.push({ type: 'node', index });
+ // If we don't have a nextSibling, keep this node so we have an end.
+ // Else, we can remove it to save future costs.
+ if (node.nextSibling === null) {
+ node.data = '';
+ }
+ else {
+ nodesToRemove.push(node);
+ index--;
+ }
+ partIndex++;
+ }
+ else {
+ let i = -1;
+ while ((i = node.data.indexOf(marker, i + 1)) !== -1) {
+ // Comment node has a binding marker inside, make an inactive part
+ // The binding won't work, but subsequent bindings will
+ // TODO (justinfagnani): consider whether it's even worth it to
+ // make bindings in comments work
+ this.parts.push({ type: 'node', index: -1 });
+ partIndex++;
+ }
+ }
+ }
+ }
+ // Remove text binding nodes after the walk to not disturb the TreeWalker
+ for (const n of nodesToRemove) {
+ n.parentNode.removeChild(n);
+ }
+ }
+}
+const endsWith = (str, suffix) => {
+ const index = str.length - suffix.length;
+ return index >= 0 && str.slice(index) === suffix;
+};
+const isTemplatePartActive = (part) => part.index !== -1;
+// Allows `document.createComment('')` to be renamed for a
+// small manual size-savings.
+const createMarker = () => document.createComment('');
+/**
+ * This regex extracts the attribute name preceding an attribute-position
+ * expression. It does this by matching the syntax allowed for attributes
+ * against the string literal directly preceding the expression, assuming that
+ * the expression is in an attribute-value position.
+ *
+ * See attributes in the HTML spec:
+ * https://www.w3.org/TR/html5/syntax.html#elements-attributes
+ *
+ * " \x09\x0a\x0c\x0d" are HTML space characters:
+ * https://www.w3.org/TR/html5/infrastructure.html#space-characters
+ *
+ * "\0-\x1F\x7F-\x9F" are Unicode control characters, which includes every
+ * space character except " ".
+ *
+ * So an attribute is:
+ * * The name: any character except a control character, space character, ('),
+ * ("), ">", "=", or "/"
+ * * Followed by zero or more space characters
+ * * Followed by "="
+ * * Followed by zero or more space characters
+ * * Followed by:
+ * * Any character except space, ('), ("), "<", ">", "=", (`), or
+ * * (") then any non-("), or
+ * * (') then any non-(')
+ */
+const lastAttributeNameRegex = /([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;
+//# sourceMappingURL=template.js.map
+
/**
* @license
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
@@ -51,7 +356,127 @@ class f{constructor(t,e,s){this.__parts=[],this.template=t,this.processor=e,this
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
- */const b=t=>null===t||!("object"==typeof t||"function"==typeof t),w=t=>Array.isArray(t)||!(!t||!t[Symbol.iterator]);class S{constructor(t,e,s){this.dirty=!0,this.element=t,this.name=e,this.strings=s,this.parts=[];for(let t=0;tthis.handleEvent(t)}setValue(t){this.__pendingValue=t}commit(){for(;e(this.__pendingValue);){const t=this.__pendingValue;this.__pendingValue=r,t(this)}if(this.__pendingValue===r)return;const t=this.__pendingValue,s=this.value,i=null==t||null!=s&&(t.capture!==s.capture||t.once!==s.once||t.passive!==s.passive),n=null!=t&&(null==s||i);i&&this.element.removeEventListener(this.eventName,this.__boundHandleEvent,this.__options),n&&(this.__options=N(t),this.element.addEventListener(this.eventName,this.__boundHandleEvent,this.__options)),this.value=t,this.__pendingValue=r}handleEvent(t){"function"==typeof this.value?this.value.call(this.eventContext||this.element,t):this.value.handleEvent(t)}}const N=t=>t&&(E?{capture:t.capture,passive:t.passive,once:t.once}:t.capture);
+ */
+/**
+ * An instance of a `Template` that can be attached to the DOM and updated
+ * with new values.
+ */
+class TemplateInstance {
+ constructor(template, processor, options) {
+ this.__parts = [];
+ this.template = template;
+ this.processor = processor;
+ this.options = options;
+ }
+ update(values) {
+ let i = 0;
+ for (const part of this.__parts) {
+ if (part !== undefined) {
+ part.setValue(values[i]);
+ }
+ i++;
+ }
+ for (const part of this.__parts) {
+ if (part !== undefined) {
+ part.commit();
+ }
+ }
+ }
+ _clone() {
+ // There are a number of steps in the lifecycle of a template instance's
+ // DOM fragment:
+ // 1. Clone - create the instance fragment
+ // 2. Adopt - adopt into the main document
+ // 3. Process - find part markers and create parts
+ // 4. Upgrade - upgrade custom elements
+ // 5. Update - set node, attribute, property, etc., values
+ // 6. Connect - connect to the document. Optional and outside of this
+ // method.
+ //
+ // We have a few constraints on the ordering of these steps:
+ // * We need to upgrade before updating, so that property values will pass
+ // through any property setters.
+ // * We would like to process before upgrading so that we're sure that the
+ // cloned fragment is inert and not disturbed by self-modifying DOM.
+ // * We want custom elements to upgrade even in disconnected fragments.
+ //
+ // Given these constraints, with full custom elements support we would
+ // prefer the order: Clone, Process, Adopt, Upgrade, Update, Connect
+ //
+ // But Safari dooes not implement CustomElementRegistry#upgrade, so we
+ // can not implement that order and still have upgrade-before-update and
+ // upgrade disconnected fragments. So we instead sacrifice the
+ // process-before-upgrade constraint, since in Custom Elements v1 elements
+ // must not modify their light DOM in the constructor. We still have issues
+ // when co-existing with CEv0 elements like Polymer 1, and with polyfills
+ // that don't strictly adhere to the no-modification rule because shadow
+ // DOM, which may be created in the constructor, is emulated by being placed
+ // in the light DOM.
+ //
+ // The resulting order is on native is: Clone, Adopt, Upgrade, Process,
+ // Update, Connect. document.importNode() performs Clone, Adopt, and Upgrade
+ // in one step.
+ //
+ // The Custom Elements v1 polyfill supports upgrade(), so the order when
+ // polyfilled is the more ideal: Clone, Process, Adopt, Upgrade, Update,
+ // Connect.
+ const fragment = isCEPolyfill ?
+ this.template.element.content.cloneNode(true) :
+ document.importNode(this.template.element.content, true);
+ const stack = [];
+ const parts = this.template.parts;
+ // Edge needs all 4 parameters present; IE11 needs 3rd parameter to be null
+ const walker = document.createTreeWalker(fragment, 133 /* NodeFilter.SHOW_{ELEMENT|COMMENT|TEXT} */, null, false);
+ let partIndex = 0;
+ let nodeIndex = 0;
+ let part;
+ let node = walker.nextNode();
+ // Loop through all the nodes and parts of a template
+ while (partIndex < parts.length) {
+ part = parts[partIndex];
+ if (!isTemplatePartActive(part)) {
+ this.__parts.push(undefined);
+ partIndex++;
+ continue;
+ }
+ // Progress the tree walker until we find our next part's node.
+ // Note that multiple parts may share the same node (attribute parts
+ // on a single element), so this loop may not run at all.
+ while (nodeIndex < part.index) {
+ nodeIndex++;
+ if (node.nodeName === 'TEMPLATE') {
+ stack.push(node);
+ walker.currentNode = node.content;
+ }
+ if ((node = walker.nextNode()) === null) {
+ // We've exhausted the content inside a nested template element.
+ // Because we still have parts (the outer for-loop), we know:
+ // - There is a template in the stack
+ // - The walker will find a nextNode outside the template
+ walker.currentNode = stack.pop();
+ node = walker.nextNode();
+ }
+ }
+ // We've arrived at our part's node.
+ if (part.type === 'node') {
+ const part = this.processor.handleTextExpression(this.options);
+ part.insertAfterNode(node.previousSibling);
+ this.__parts.push(part);
+ }
+ else {
+ this.__parts.push(...this.processor.handleAttributeExpressions(node, part.name, part.strings, this.options));
+ }
+ partIndex++;
+ }
+ if (isCEPolyfill) {
+ document.adoptNode(fragment);
+ customElements.upgrade(fragment);
+ }
+ return fragment;
+ }
+}
+//# sourceMappingURL=template-instance.js.map
+
/**
* @license
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
@@ -64,7 +489,103 @@ class f{constructor(t,e,s){this.__parts=[],this.template=t,this.processor=e,this
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
- */const T=new class{handleAttributeExpressions(t,e,s,i){const n=e[0];if("."===n){return new k(t,e.slice(1),s).parts}return"@"===n?[new M(t,e.slice(1),i.eventContext)]:"?"===n?[new $(t,e.slice(1),s)]:new S(t,e,s).parts}handleTextExpression(t){return new C(t)}};
+ */
+const commentMarker = ` ${marker} `;
+/**
+ * The return type of `html`, which holds a Template and the values from
+ * interpolated expressions.
+ */
+class TemplateResult {
+ constructor(strings, values, type, processor) {
+ this.strings = strings;
+ this.values = values;
+ this.type = type;
+ this.processor = processor;
+ }
+ /**
+ * Returns a string of HTML used to create a `` element.
+ */
+ getHTML() {
+ const l = this.strings.length - 1;
+ let html = '';
+ let isCommentBinding = false;
+ for (let i = 0; i < l; i++) {
+ const s = this.strings[i];
+ // For each binding we want to determine the kind of marker to insert
+ // into the template source before it's parsed by the browser's HTML
+ // parser. The marker type is based on whether the expression is in an
+ // attribute, text, or comment poisition.
+ // * For node-position bindings we insert a comment with the marker
+ // sentinel as its text content, like .
+ // * For attribute bindings we insert just the marker sentinel for the
+ // first binding, so that we support unquoted attribute bindings.
+ // Subsequent bindings can use a comment marker because multi-binding
+ // attributes must be quoted.
+ // * For comment bindings we insert just the marker sentinel so we don't
+ // close the comment.
+ //
+ // The following code scans the template source, but is *not* an HTML
+ // parser. We don't need to track the tree structure of the HTML, only
+ // whether a binding is inside a comment, and if not, if it appears to be
+ // the first binding in an attribute.
+ const commentOpen = s.lastIndexOf('', commentOpen + 1) === -1;
+ // Check to see if we have an attribute-like sequence preceeding the
+ // expression. This can match "name=value" like structures in text,
+ // comments, and attribute values, so there can be false-positives.
+ const attributeMatch = lastAttributeNameRegex.exec(s);
+ if (attributeMatch === null) {
+ // We're only in this branch if we don't have a attribute-like
+ // preceeding sequence. For comments, this guards against unusual
+ // attribute values like
. Cases like
+ // are handled correctly in the attribute branch
+ // below.
+ html += s + (isCommentBinding ? commentMarker : nodeMarker);
+ }
+ else {
+ // For attributes we use just a marker sentinel, and also append a
+ // $lit$ suffix to the name to opt-out of attribute-specific parsing
+ // that IE and Edge do for style and certain SVG attributes.
+ html += s.substr(0, attributeMatch.index) + attributeMatch[1] +
+ attributeMatch[2] + boundAttributeSuffix + attributeMatch[3] +
+ marker;
+ }
+ }
+ html += this.strings[l];
+ return html;
+ }
+ getTemplateElement() {
+ const template = document.createElement('template');
+ template.innerHTML = this.getHTML();
+ return template;
+ }
+}
+/**
+ * A TemplateResult for SVG fragments.
+ *
+ * This class wraps HTML in an `