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(;l0;){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`` + * ``` + * + * 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`${super.getHTML()}`}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 `