. 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;
}
}
/**
* @license
* Copyright (c) 2017 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
*/
const isPrimitive = (value) => {
return (value === null ||
!(typeof value === 'object' || typeof value === 'function'));
};
const isIterable = (value) => {
return Array.isArray(value) ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
!!(value && value[Symbol.iterator]);
};
/**
* Writes attribute values to the DOM for a group of AttributeParts bound to a
* single attribute. The value is only set once even if there are multiple parts
* for an attribute.
*/
class AttributeCommitter {
constructor(element, name, strings) {
this.dirty = true;
this.element = element;
this.name = name;
this.strings = strings;
this.parts = [];
for (let i = 0; i < strings.length - 1; i++) {
this.parts[i] = this._createPart();
}
}
/**
* Creates a single part. Override this to create a differnt type of part.
*/
_createPart() {
return new AttributePart(this);
}
_getValue() {
const strings = this.strings;
const l = strings.length - 1;
let text = '';
for (let i = 0; i < l; i++) {
text += strings[i];
const part = this.parts[i];
if (part !== undefined) {
const v = part.value;
if (isPrimitive(v) || !isIterable(v)) {
text += typeof v === 'string' ? v : String(v);
}
else {
for (const t of v) {
text += typeof t === 'string' ? t : String(t);
}
}
}
}
text += strings[l];
return text;
}
commit() {
if (this.dirty) {
this.dirty = false;
this.element.setAttribute(this.name, this._getValue());
}
}
}
/**
* A Part that controls all or part of an attribute value.
*/
class AttributePart {
constructor(committer) {
this.value = undefined;
this.committer = committer;
}
setValue(value) {
if (value !== noChange && (!isPrimitive(value) || value !== this.value)) {
this.value = value;
// If the value is a not a directive, dirty the committer so that it'll
// call setAttribute. If the value is a directive, it'll dirty the
// committer if it calls setValue().
if (!isDirective(value)) {
this.committer.dirty = true;
}
}
}
commit() {
while (isDirective(this.value)) {
const directive = this.value;
this.value = noChange;
directive(this);
}
if (this.value === noChange) {
return;
}
this.committer.commit();
}
}
/**
* A Part that controls a location within a Node tree. Like a Range, NodePart
* has start and end locations and can set and update the Nodes between those
* locations.
*
* NodeParts support several value types: primitives, Nodes, TemplateResults,
* as well as arrays and iterables of those types.
*/
class NodePart {
constructor(options) {
this.value = undefined;
this.__pendingValue = undefined;
this.options = options;
}
/**
* Appends this part into a container.
*
* This part must be empty, as its contents are not automatically moved.
*/
appendInto(container) {
this.startNode = container.appendChild(createMarker());
this.endNode = container.appendChild(createMarker());
}
/**
* Inserts this part after the `ref` node (between `ref` and `ref`'s next
* sibling). Both `ref` and its next sibling must be static, unchanging nodes
* such as those that appear in a literal section of a template.
*
* This part must be empty, as its contents are not automatically moved.
*/
insertAfterNode(ref) {
this.startNode = ref;
this.endNode = ref.nextSibling;
}
/**
* Appends this part into a parent part.
*
* This part must be empty, as its contents are not automatically moved.
*/
appendIntoPart(part) {
part.__insert(this.startNode = createMarker());
part.__insert(this.endNode = createMarker());
}
/**
* Inserts this part after the `ref` part.
*
* This part must be empty, as its contents are not automatically moved.
*/
insertAfterPart(ref) {
ref.__insert(this.startNode = createMarker());
this.endNode = ref.endNode;
ref.endNode = this.startNode;
}
setValue(value) {
this.__pendingValue = value;
}
commit() {
if (this.startNode.parentNode === null) {
return;
}
while (isDirective(this.__pendingValue)) {
const directive = this.__pendingValue;
this.__pendingValue = noChange;
directive(this);
}
const value = this.__pendingValue;
if (value === noChange) {
return;
}
if (isPrimitive(value)) {
if (value !== this.value) {
this.__commitText(value);
}
}
else if (value instanceof TemplateResult) {
this.__commitTemplateResult(value);
}
else if (value instanceof Node) {
this.__commitNode(value);
}
else if (isIterable(value)) {
this.__commitIterable(value);
}
else if (value === nothing) {
this.value = nothing;
this.clear();
}
else {
// Fallback, will render the string representation
this.__commitText(value);
}
}
__insert(node) {
this.endNode.parentNode.insertBefore(node, this.endNode);
}
__commitNode(value) {
if (this.value === value) {
return;
}
this.clear();
this.__insert(value);
this.value = value;
}
__commitText(value) {
const node = this.startNode.nextSibling;
value = value == null ? '' : value;
// If `value` isn't already a string, we explicitly convert it here in case
// it can't be implicitly converted - i.e. it's a symbol.
const valueAsString = typeof value === 'string' ? value : String(value);
if (node === this.endNode.previousSibling &&
node.nodeType === 3 /* Node.TEXT_NODE */) {
// If we only have a single text node between the markers, we can just
// set its value, rather than replacing it.
// TODO(justinfagnani): Can we just check if this.value is primitive?
node.data = valueAsString;
}
else {
this.__commitNode(document.createTextNode(valueAsString));
}
this.value = value;
}
__commitTemplateResult(value) {
const template = this.options.templateFactory(value);
if (this.value instanceof TemplateInstance &&
this.value.template === template) {
this.value.update(value.values);
}
else {
// Make sure we propagate the template processor from the TemplateResult
// so that we use its syntax extension, etc. The template factory comes
// from the render function options so that it can control template
// caching and preprocessing.
const instance = new TemplateInstance(template, value.processor, this.options);
const fragment = instance._clone();
instance.update(value.values);
this.__commitNode(fragment);
this.value = instance;
}
}
__commitIterable(value) {
// For an Iterable, we create a new InstancePart per item, then set its
// value to the item. This is a little bit of overhead for every item in
// an Iterable, but it lets us recurse easily and efficiently update Arrays
// of TemplateResults that will be commonly returned from expressions like:
// array.map((i) => html`${i}`), by reusing existing TemplateInstances.
// If _value is an array, then the previous render was of an
// iterable and _value will contain the NodeParts from the previous
// render. If _value is not an array, clear this part and make a new
// array for NodeParts.
if (!Array.isArray(this.value)) {
this.value = [];
this.clear();
}
// Lets us keep track of how many items we stamped so we can clear leftover
// items from a previous render
const itemParts = this.value;
let partIndex = 0;
let itemPart;
for (const item of value) {
// Try to reuse an existing part
itemPart = itemParts[partIndex];
// If no existing part, create a new one
if (itemPart === undefined) {
itemPart = new NodePart(this.options);
itemParts.push(itemPart);
if (partIndex === 0) {
itemPart.appendIntoPart(this);
}
else {
itemPart.insertAfterPart(itemParts[partIndex - 1]);
}
}
itemPart.setValue(item);
itemPart.commit();
partIndex++;
}
if (partIndex < itemParts.length) {
// Truncate the parts array so _value reflects the current state
itemParts.length = partIndex;
this.clear(itemPart && itemPart.endNode);
}
}
clear(startNode = this.startNode) {
removeNodes(this.startNode.parentNode, startNode.nextSibling, this.endNode);
}
}
/**
* Implements a boolean attribute, roughly as defined in the HTML
* specification.
*
* If the value is truthy, then the attribute is present with a value of
* ''. If the value is falsey, the attribute is removed.
*/
class BooleanAttributePart {
constructor(element, name, strings) {
this.value = undefined;
this.__pendingValue = undefined;
if (strings.length !== 2 || strings[0] !== '' || strings[1] !== '') {
throw new Error('Boolean attributes can only contain a single expression');
}
this.element = element;
this.name = name;
this.strings = strings;
}
setValue(value) {
this.__pendingValue = value;
}
commit() {
while (isDirective(this.__pendingValue)) {
const directive = this.__pendingValue;
this.__pendingValue = noChange;
directive(this);
}
if (this.__pendingValue === noChange) {
return;
}
const value = !!this.__pendingValue;
if (this.value !== value) {
if (value) {
this.element.setAttribute(this.name, '');
}
else {
this.element.removeAttribute(this.name);
}
this.value = value;
}
this.__pendingValue = noChange;
}
}
/**
* Sets attribute values for PropertyParts, so that the value is only set once
* even if there are multiple parts for a property.
*
* If an expression controls the whole property value, then the value is simply
* assigned to the property under control. If there are string literals or
* multiple expressions, then the strings are expressions are interpolated into
* a string first.
*/
class PropertyCommitter extends AttributeCommitter {
constructor(element, name, strings) {
super(element, name, strings);
this.single =
(strings.length === 2 && strings[0] === '' && strings[1] === '');
}
_createPart() {
return new PropertyPart(this);
}
_getValue() {
if (this.single) {
return this.parts[0].value;
}
return super._getValue();
}
commit() {
if (this.dirty) {
this.dirty = false;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.element[this.name] = this._getValue();
}
}
}
class PropertyPart extends AttributePart {
}
// Detect event listener options support. If the `capture` property is read
// from the options object, then options are supported. If not, then the third
// argument to add/removeEventListener is interpreted as the boolean capture
// value so we should only pass the `capture` property.
let eventOptionsSupported = false;
// Wrap into an IIFE because MS Edge <= v41 does not support having try/catch
// blocks right into the body of a module
(() => {
try {
const options = {
get capture() {
eventOptionsSupported = true;
return false;
}
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
window.addEventListener('test', options, options);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
window.removeEventListener('test', options, options);
}
catch (_e) {
// event options not supported
}
})();
class EventPart {
constructor(element, eventName, eventContext) {
this.value = undefined;
this.__pendingValue = undefined;
this.element = element;
this.eventName = eventName;
this.eventContext = eventContext;
this.__boundHandleEvent = (e) => this.handleEvent(e);
}
setValue(value) {
this.__pendingValue = value;
}
commit() {
while (isDirective(this.__pendingValue)) {
const directive = this.__pendingValue;
this.__pendingValue = noChange;
directive(this);
}
if (this.__pendingValue === noChange) {
return;
}
const newListener = this.__pendingValue;
const oldListener = this.value;
const shouldRemoveListener = newListener == null ||
oldListener != null &&
(newListener.capture !== oldListener.capture ||
newListener.once !== oldListener.once ||
newListener.passive !== oldListener.passive);
const shouldAddListener = newListener != null && (oldListener == null || shouldRemoveListener);
if (shouldRemoveListener) {
this.element.removeEventListener(this.eventName, this.__boundHandleEvent, this.__options);
}
if (shouldAddListener) {
this.__options = getOptions(newListener);
this.element.addEventListener(this.eventName, this.__boundHandleEvent, this.__options);
}
this.value = newListener;
this.__pendingValue = noChange;
}
handleEvent(event) {
if (typeof this.value === 'function') {
this.value.call(this.eventContext || this.element, event);
}
else {
this.value.handleEvent(event);
}
}
}
// We copy options because of the inconsistent behavior of browsers when reading
// the third argument of add/removeEventListener. IE11 doesn't support options
// at all. Chrome 41 only reads `capture` if the argument is an object.
const getOptions = (o) => o &&
(eventOptionsSupported ?
{ capture: o.capture, passive: o.passive, once: o.once } :
o.capture);
/**
* @license
* Copyright (c) 2017 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
*/
/**
* The default TemplateFactory which caches Templates keyed on
* result.type and result.strings.
*/
function templateFactory(result) {
let templateCache = templateCaches.get(result.type);
if (templateCache === undefined) {
templateCache = {
stringsArray: new WeakMap(),
keyString: new Map()
};
templateCaches.set(result.type, templateCache);
}
let template = templateCache.stringsArray.get(result.strings);
if (template !== undefined) {
return template;
}
// If the TemplateStringsArray is new, generate a key from the strings
// This key is shared between all templates with identical content
const key = result.strings.join(marker);
// Check if we already have a Template for this key
template = templateCache.keyString.get(key);
if (template === undefined) {
// If we have not seen this key before, create a new Template
template = new Template(result, result.getTemplateElement());
// Cache the Template for this key
templateCache.keyString.set(key, template);
}
// Cache all future queries for this TemplateStringsArray
templateCache.stringsArray.set(result.strings, template);
return template;
}
const templateCaches = new Map();
/**
* @license
* Copyright (c) 2017 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
*/
const parts = new WeakMap();
/**
* Renders a template result or other value to a container.
*
* To update a container with new values, reevaluate the template literal and
* call `render` with the new result.
*
* @param result Any value renderable by NodePart - typically a TemplateResult
* created by evaluating a template tag like `html` or `svg`.
* @param container A DOM parent to render to. The entire contents are either
* replaced, or efficiently updated if the same result type was previous
* rendered there.
* @param options RenderOptions for the entire render tree rendered to this
* container. Render options must *not* change between renders to the same
* container, as those changes will not effect previously rendered DOM.
*/
const render = (result, container, options) => {
let part = parts.get(container);
if (part === undefined) {
removeNodes(container, container.firstChild);
parts.set(container, part = new NodePart(Object.assign({ templateFactory }, options)));
part.appendInto(container);
}
part.setValue(result);
part.commit();
};
/**
* @license
* Copyright (c) 2017 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
*/
/**
* Creates Parts when a template is instantiated.
*/
class DefaultTemplateProcessor {
/**
* Create parts for an attribute-position binding, given the event, attribute
* name, and string literals.
*
* @param element The element containing the binding
* @param name The attribute name
* @param strings The string literals. There are always at least two strings,
* event for fully-controlled bindings with a single expression.
*/
handleAttributeExpressions(element, name, strings, options) {
const prefix = name[0];
if (prefix === '.') {
const committer = new PropertyCommitter(element, name.slice(1), strings);
return committer.parts;
}
if (prefix === '@') {
return [new EventPart(element, name.slice(1), options.eventContext)];
}
if (prefix === '?') {
return [new BooleanAttributePart(element, name.slice(1), strings)];
}
const committer = new AttributeCommitter(element, name, strings);
return committer.parts;
}
/**
* Create parts for a text-position binding.
* @param templateFactory
*/
handleTextExpression(options) {
return new NodePart(options);
}
}
const defaultTemplateProcessor = new DefaultTemplateProcessor();
/**
* @license
* Copyright (c) 2017 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
*/
// IMPORTANT: do not change the property name or the assignment expression.
// This line will be used in regexes to search for lit-html usage.
// TODO(justinfagnani): inject version number at build time
if (typeof window !== 'undefined') {
(window['litHtmlVersions'] || (window['litHtmlVersions'] = [])).push('1.2.1');
}
/**
* Interprets a template literal as an HTML template that can efficiently
* render to and update a container.
*/
const html = (strings, ...values) => new TemplateResult(strings, values, 'html', defaultTemplateProcessor);
/**
* @license
* Copyright (c) 2017 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
*/
// Get a key to lookup in `templateCaches`.
const getTemplateCacheKey = (type, scopeName) => `${type}--${scopeName}`;
let compatibleShadyCSSVersion = true;
if (typeof window.ShadyCSS === 'undefined') {
compatibleShadyCSSVersion = false;
}
else if (typeof window.ShadyCSS.prepareTemplateDom === 'undefined') {
console.warn(`Incompatible ShadyCSS version detected. ` +
`Please update to at least @webcomponents/webcomponentsjs@2.0.2 and ` +
`@webcomponents/shadycss@1.3.1.`);
compatibleShadyCSSVersion = false;
}
/**
* Template factory which scopes template DOM using ShadyCSS.
* @param scopeName {string}
*/
const shadyTemplateFactory = (scopeName) => (result) => {
const cacheKey = getTemplateCacheKey(result.type, scopeName);
let templateCache = templateCaches.get(cacheKey);
if (templateCache === undefined) {
templateCache = {
stringsArray: new WeakMap(),
keyString: new Map()
};
templateCaches.set(cacheKey, templateCache);
}
let template = templateCache.stringsArray.get(result.strings);
if (template !== undefined) {
return template;
}
const key = result.strings.join(marker);
template = templateCache.keyString.get(key);
if (template === undefined) {
const element = result.getTemplateElement();
if (compatibleShadyCSSVersion) {
window.ShadyCSS.prepareTemplateDom(element, scopeName);
}
template = new Template(result, element);
templateCache.keyString.set(key, template);
}
templateCache.stringsArray.set(result.strings, template);
return template;
};
const TEMPLATE_TYPES = ['html', 'svg'];
/**
* Removes all style elements from Templates for the given scopeName.
*/
const removeStylesFromLitTemplates = (scopeName) => {
TEMPLATE_TYPES.forEach((type) => {
const templates = templateCaches.get(getTemplateCacheKey(type, scopeName));
if (templates !== undefined) {
templates.keyString.forEach((template) => {
const { element: { content } } = template;
// IE 11 doesn't support the iterable param Set constructor
const styles = new Set();
Array.from(content.querySelectorAll('style')).forEach((s) => {
styles.add(s);
});
removeNodesFromTemplate(template, styles);
});
}
});
};
const shadyRenderSet = new Set();
/**
* For the given scope name, ensures that ShadyCSS style scoping is performed.
* This is done just once per scope name so the fragment and template cannot
* be modified.
* (1) extracts styles from the rendered fragment and hands them to ShadyCSS
* to be scoped and appended to the document
* (2) removes style elements from all lit-html Templates for this scope name.
*
* Note,
`;
/* eslint no-console: 0 */
console.info(`%c BAR-CARD \n%c ${localize('common.version')} ${CARD_VERSION} `, 'color: orange; font-weight: bold; background: black', 'color: white; font-weight: bold; background: dimgray');
// TODO Name your custom element
let BarCard = class BarCard extends LitElement {
constructor() {
super(...arguments);
this._configArray = [];
this._stateArray = [];
this._animationState = [];
this._rowAmount = 1;
}
static async getConfigElement() {
return document.createElement('bar-card-editor');
}
static getStubConfig() {
return {};
}
shouldUpdate(changedProps) {
return hasConfigOrEntitiesChanged(this, changedProps, false);
}
setConfig(config) {
if (!config) {
throw new Error(localize('common.invalid_configuration'));
}
this._config = mergeDeep({
animation: {
state: 'off',
speed: 5,
},
color: 'var(--bar-card-color, var(--primary-color))',
columns: 1,
direction: 'right',
max: 100,
min: 0,
positions: {
icon: 'outside',
indicator: 'outside',
name: 'inside',
minmax: 'off',
value: 'inside',
},
}, config);
if (this._config.stack == 'horizontal')
this._config.columns = this._config.entities.length;
this._configArray = createConfigArray(this._config);
this._rowAmount = this._configArray.length / this._config.columns;
}
render() {
if (!this._config || !this.hass) {
return html ``;
}
return html `
${this._createBarArray()}
${styles}
`;
}
_createBarArray() {
// Create array containing number of bars per row.
const columnsArray = [];
for (let i = 0; i < this._configArray.length; i++) {
if ((columnsArray.length + 1) * this._config.columns == i) {
columnsArray.push(this._config.columns);
}
if (this._configArray.length == i + 1) {
columnsArray.push(this._configArray.length - columnsArray.length * this._config.columns);
}
}
// For each row add contained bars based on columnsArray.
const perRowArray = [];
for (let i = 0; i < columnsArray.length; i++) {
// For every number in columnsArray add bars.
const currentRowArray = [];
for (let x = 0; x < columnsArray[i]; x++) {
const index = i * this._config.columns + x;
const config = this._configArray[index];
const state = this.hass.states[config.entity];
if (!state) {
currentRowArray.push(html `
${localize('common.entity_not_available')}: ${config.entity}
`);
continue;
}
// If attribute is defined use attribute value as bar value.
let entityState;
if (config.attribute) {
entityState = state.attributes[config.attribute];
}
else {
entityState = state.state;
}
// Contine if severity hide is defined.
if (config.severity) {
if (this._computeSeverityVisibility(entityState, index)) {
continue;
}
}
// If limit_value is defined limit the displayed value to min and max.
if (config.limit_value) {
entityState = Math.min(entityState, config.max);
entityState = Math.max(entityState, config.min);
}
// If decimal is defined check if NaN and apply number fix.
if (!isNaN(Number(entityState))) {
if (config.decimal == 0)
entityState = Number(entityState).toFixed(0);
else if (config.decimal)
entityState = Number(entityState).toFixed(config.decimal);
}
// Defined height and check for configured height.
let barHeight = 40;
if (config.height)
barHeight = config.height;
// Set style variables based on direction.
let alignItems = 'stretch';
let backgroundMargin = '0px 0px 0px 13px';
let barDirection = 'right';
let flexDirection = 'row';
let markerDirection = 'left';
let markerStyle = 'height: 100%; width: 2px;';
switch (config.direction) {
case 'right':
barDirection = 'right';
markerDirection = 'left';
break;
case 'up':
backgroundMargin = '0px';
barDirection = 'top';
flexDirection = 'column-reverse';
markerDirection = 'bottom';
markerStyle = 'height: 2px; width: 100%;';
break;
}
// Set icon position html.
let iconOutside;
let iconInside;
let icon;
if (this._computeSeverityIcon(entityState, index)) {
icon = this._computeSeverityIcon(entityState, index);
}
else if (config.icon) {
icon = config.icon;
}
else if (state.attributes.icon) {
icon = state.attributes.icon;
}
else {
icon = O(d(config.entity), entityState);
}
switch (config.positions.icon) {
case 'outside':
iconOutside = html `
`;
break;
case 'inside':
iconInside = html `
`;
backgroundMargin = '0px';
break;
case 'off':
backgroundMargin = '0px';
break;
}
// Check for configured name otherwise use friendly name.
const name = config.name ? config.name : state.attributes.friendly_name;
// Set name html based on position.
let nameOutside;
let nameInside;
switch (config.positions.name) {
case 'outside':
nameOutside = html `
${name}
`;
backgroundMargin = '0px';
break;
case 'inside':
nameInside = html `
${name}
`;
break;
}
// Check for configured unit of measurement otherwise use attribute value.
let unitOfMeasurement;
if (isNaN(Number(entityState))) {
unitOfMeasurement = '';
}
else {
if (config.unit_of_measurement) {
unitOfMeasurement = config.unit_of_measurement;
}
else {
unitOfMeasurement = state.attributes.unit_of_measurement;
}
}
// Set min and max html based on position.
let minMaxOutside;
let minMaxInside;
switch (config.positions.minmax) {
case 'outside':
minMaxOutside = html `
${config.min}${unitOfMeasurement}
/
${config.max}${unitOfMeasurement}
`;
break;
case 'inside':
minMaxInside = html `
${config.min}${unitOfMeasurement}
/
${config.max}${unitOfMeasurement}
`;
break;
}
// Set value html based on position.
let valueOutside;
let valueInside;
switch (config.positions.value) {
case 'outside':
valueOutside = html `
${config.complementary ? config.max - entityState : entityState} ${unitOfMeasurement}
`;
break;
case 'inside':
valueInside = html `
${config.complementary ? config.max - entityState : entityState} ${unitOfMeasurement}
`;
break;
case 'off':
backgroundMargin = '0px';
break;
}
// Set indicator and animation state based on value change.
let indicatorText = '';
if (entityState > this._stateArray[index]) {
indicatorText = '▲';
if (config.direction == 'up')
this._animationState[index] = 'animation-increase-vertical';
else
this._animationState[index] = 'animation-increase';
}
else if (entityState < this._stateArray[index]) {
indicatorText = '▼';
if (config.direction == 'up')
this._animationState[index] = 'animation-decrease-vertical';
else
this._animationState[index] = 'animation-decrease';
}
else {
this._animationState[index] = this._animationState[index];
}
if (isNaN(Number(entityState))) {
indicatorText = '';
}
// Set bar color.
const barColor = this._computeBarColor(entityState, index);
// Set indicator html based on position.
let indicatorOutside;
let indicatorInside;
switch (config.positions.indicator) {
case 'outside':
indicatorOutside = html `
${indicatorText}
`;
break;
case 'inside':
indicatorInside = html `
${indicatorText}
`;
break;
}
// Set bar percent and marker percent based on value difference.
const barPercent = this._computePercent(entityState, index);
const targetMarkerPercent = this._computePercent(config.target, index);
let targetStartPercent = barPercent;
let targetEndPercent = this._computePercent(config.target, index);
if (targetEndPercent < targetStartPercent) {
targetStartPercent = targetEndPercent;
targetEndPercent = barPercent;
}
// Set bar width if configured.
let barWidth = '';
if (config.width) {
alignItems = 'center';
barWidth = `width: ${config.width}`;
}
// Set animation state inside array.
const animation = this._animationState[index];
let animationDirection = 'right';
let animationPercent = barPercent * 100;
let animationClass = 'animationbar-horizontal';
if (animation == 'animation-increase-vertical' || animation == 'animation-decrease-vertical') {
animationDirection = 'bottom';
animationClass = 'animationbar-vertical';
animationPercent = (100 - barPercent) * 100;
}
// Add current bar to row array.
currentRowArray.push(html `
${iconOutside} ${indicatorOutside} ${nameOutside}
${config.animation.state == 'on'
? html `
`
: ''}
${config.target
? html `
`
: ''}
${iconInside} ${indicatorInside} ${nameInside} ${minMaxInside} ${valueInside}
${minMaxOutside} ${valueOutside}
`);
// Set entity state inside array if changed.
if (entityState !== this._stateArray[index]) {
this._stateArray[index] = entityState;
}
}
// Add all bars for this row to array.
perRowArray.push(currentRowArray);
}
// Create array containing all rows.
let rowFlexDirection = 'column';
if (this._config.columns || this._config.stack)
rowFlexDirection = 'row';
const rowArray = [];
for (const row of perRowArray) {
rowArray.push(html `
${row}
`);
}
return rowArray;
}
_computeBarColor(value, index) {
const config = this._configArray[index];
let barColor;
if (config.severity) {
barColor = this._computeSeverityColor(value, index);
}
else if (value == 'unavailable') {
barColor = `var(--bar-card-disabled-color, ${config.color})`;
}
else {
barColor = config.color;
}
return barColor;
}
_computeSeverityColor(value, index) {
const config = this._configArray[index];
const numberValue = Number(value);
const sections = config.severity;
let color;
if (isNaN(numberValue)) {
sections.forEach(section => {
if (value == section.text) {
color = section.color;
}
});
}
else {
sections.forEach(section => {
if (numberValue >= section.from && numberValue <= section.to) {
color = section.color;
}
});
}
if (color == undefined)
color = config.color;
return color;
}
_computeSeverityVisibility(value, index) {
const config = this._configArray[index];
const numberValue = Number(value);
const sections = config.severity;
let hide = false;
if (isNaN(numberValue)) {
sections.forEach(section => {
if (value == section.text) {
hide = section.hide;
}
});
}
else {
sections.forEach(section => {
if (numberValue >= section.from && numberValue <= section.to) {
hide = section.hide;
}
});
}
return hide;
}
_computeSeverityIcon(value, index) {
const config = this._configArray[index];
const numberValue = Number(value);
const sections = config.severity;
let icon = false;
if (!sections)
return false;
if (isNaN(numberValue)) {
sections.forEach(section => {
if (value == section.text) {
icon = section.icon;
}
});
}
else {
sections.forEach(section => {
if (numberValue >= section.from && numberValue <= section.to) {
icon = section.icon;
}
});
}
return icon;
}
_computePercent(value, index) {
const config = this._configArray[index];
const numberValue = Number(value);
if (value == 'unavailable')
return 0;
if (isNaN(numberValue))
return 100;
switch (config.direction) {
case 'right-reverse':
case 'left-reverse':
case 'up-reverse':
case 'down-reverse':
return 100 - (100 * (numberValue - config.min)) / (config.max - config.min);
default:
return (100 * (numberValue - config.min)) / (config.max - config.min);
}
}
_handleAction(ev) {
if (this.hass && ev.target.config && ev.detail.action) {
U(this, this.hass, ev.target.config, ev.detail.action);
}
}
getCardSize() {
if (this._config.height) {
const heightString = this._config.height.toString();
const cardSize = Math.trunc((Number(heightString.replace('px', '')) / 50) * this._rowAmount);
return cardSize + 1;
}
else {
return this._rowAmount + 1;
}
}
};
__decorate([
property()
], BarCard.prototype, "hass", void 0);
__decorate([
property()
], BarCard.prototype, "_config", void 0);
__decorate([
property()
], BarCard.prototype, "_configArray", void 0);
BarCard = __decorate([
customElement('bar-card')
], BarCard);
export { BarCard };