|
| 1 | +console.log( |
| 2 | + `%cvertical-stack-in-card\n%cVersion: ${'1.0.1'}`, |
| 3 | + 'color: #1976d2; font-weight: bold;', |
| 4 | + '' |
| 5 | +); |
| 6 | + |
| 7 | +class VerticalStackInCard extends HTMLElement { |
| 8 | + constructor() { |
| 9 | + super(); |
| 10 | + } |
| 11 | + |
| 12 | + setConfig(config) { |
| 13 | + this._cardSize = {}; |
| 14 | + this._cardSize.promise = new Promise( |
| 15 | + (resolve) => (this._cardSize.resolve = resolve) |
| 16 | + ); |
| 17 | + |
| 18 | + if (!config || !config.cards || !Array.isArray(config.cards)) { |
| 19 | + throw new Error('Card config incorrect'); |
| 20 | + } |
| 21 | + this._config = config; |
| 22 | + this._refCards = []; |
| 23 | + this.renderCard(); |
| 24 | + } |
| 25 | + |
| 26 | + async renderCard() { |
| 27 | + const config = this._config; |
| 28 | + const promises = config.cards.map((config) => |
| 29 | + this._createCardElement(config) |
| 30 | + ); |
| 31 | + this._refCards = await Promise.all(promises); |
| 32 | + |
| 33 | + // Style cards |
| 34 | + this._refCards.forEach((card) => { |
| 35 | + if (card.updateComplete) { |
| 36 | + card.updateComplete.then(() => this._styleCard(card)); |
| 37 | + } else { |
| 38 | + this._styleCard(card); |
| 39 | + } |
| 40 | + }); |
| 41 | + |
| 42 | + // Create the card |
| 43 | + const card = document.createElement('ha-card'); |
| 44 | + const cardContent = document.createElement('div'); |
| 45 | + card.header = config.title; |
| 46 | + card.style.overflow = 'hidden'; |
| 47 | + this._refCards.forEach((card) => cardContent.appendChild(card)); |
| 48 | + if (config.horizontal) { |
| 49 | + cardContent.style.display = 'flex'; |
| 50 | + cardContent.childNodes.forEach((card) => { |
| 51 | + card.style.flex = '1 1 0'; |
| 52 | + card.style.minWidth = 0; |
| 53 | + }); |
| 54 | + } |
| 55 | + card.appendChild(cardContent); |
| 56 | + |
| 57 | + const shadowRoot = this.shadowRoot || this.attachShadow({ mode: 'open' }); |
| 58 | + while (shadowRoot.hasChildNodes()) { |
| 59 | + shadowRoot.removeChild(shadowRoot.lastChild); |
| 60 | + } |
| 61 | + shadowRoot.appendChild(card); |
| 62 | + |
| 63 | + // Calculate card size |
| 64 | + this._cardSize.resolve(); |
| 65 | + } |
| 66 | + |
| 67 | + async _createCardElement(cardConfig) { |
| 68 | + const helpers = await window.loadCardHelpers(); |
| 69 | + const element = |
| 70 | + cardConfig.type === 'divider' |
| 71 | + ? helpers.createRowElement(cardConfig) |
| 72 | + : helpers.createCardElement(cardConfig); |
| 73 | + |
| 74 | + element.hass = this._hass; |
| 75 | + element.addEventListener( |
| 76 | + 'll-rebuild', |
| 77 | + (ev) => { |
| 78 | + ev.stopPropagation(); |
| 79 | + this._createCardElement(cardConfig).then(() => { |
| 80 | + this.renderCard(); |
| 81 | + }); |
| 82 | + }, |
| 83 | + { once: true } |
| 84 | + ); |
| 85 | + return element; |
| 86 | + } |
| 87 | + |
| 88 | + set hass(hass) { |
| 89 | + this._hass = hass; |
| 90 | + if (this._refCards) { |
| 91 | + this._refCards.forEach((card) => { |
| 92 | + card.hass = hass; |
| 93 | + }); |
| 94 | + } |
| 95 | + } |
| 96 | + |
| 97 | + _styleCard(element) { |
| 98 | + const config = this._config; |
| 99 | + if (element.shadowRoot) { |
| 100 | + if (element.shadowRoot.querySelector('ha-card')) { |
| 101 | + let ele = element.shadowRoot.querySelector('ha-card'); |
| 102 | + ele.style.boxShadow = 'none'; |
| 103 | + ele.style.borderRadius = '0'; |
| 104 | + ele.style.border = 'none'; |
| 105 | + if ('styles' in config) { |
| 106 | + Object.entries(config.styles).forEach(([key, value]) => |
| 107 | + ele.style.setProperty(key, value) |
| 108 | + ); |
| 109 | + } |
| 110 | + } else { |
| 111 | + let searchEles = element.shadowRoot.getElementById('root'); |
| 112 | + if (!searchEles) { |
| 113 | + searchEles = element.shadowRoot.getElementById('card'); |
| 114 | + } |
| 115 | + if (!searchEles) return; |
| 116 | + searchEles = searchEles.childNodes; |
| 117 | + for (let i = 0; i < searchEles.length; i++) { |
| 118 | + if (searchEles[i].style) { |
| 119 | + searchEles[i].style.margin = '0px'; |
| 120 | + } |
| 121 | + this._styleCard(searchEles[i]); |
| 122 | + } |
| 123 | + } |
| 124 | + } else { |
| 125 | + if ( |
| 126 | + typeof element.querySelector === 'function' && |
| 127 | + element.querySelector('ha-card') |
| 128 | + ) { |
| 129 | + let ele = element.querySelector('ha-card'); |
| 130 | + ele.style.boxShadow = 'none'; |
| 131 | + ele.style.borderRadius = '0'; |
| 132 | + ele.style.border = 'none'; |
| 133 | + if ('styles' in config) { |
| 134 | + Object.entries(config.styles).forEach(([key, value]) => |
| 135 | + ele.style.setProperty(key, value) |
| 136 | + ); |
| 137 | + } |
| 138 | + } |
| 139 | + let searchEles = element.childNodes; |
| 140 | + for (let i = 0; i < searchEles.length; i++) { |
| 141 | + if (searchEles[i] && searchEles[i].style) { |
| 142 | + searchEles[i].style.margin = '0px'; |
| 143 | + } |
| 144 | + this._styleCard(searchEles[i]); |
| 145 | + } |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + _computeCardSize(card) { |
| 150 | + if (typeof card.getCardSize === 'function') { |
| 151 | + return card.getCardSize(); |
| 152 | + } |
| 153 | + return customElements |
| 154 | + .whenDefined(card.localName) |
| 155 | + .then(() => this._computeCardSize(card)) |
| 156 | + .catch(() => 1); |
| 157 | + } |
| 158 | + |
| 159 | + async getCardSize() { |
| 160 | + await this._cardSize.promise; |
| 161 | + const sizes = await Promise.all(this._refCards.map(this._computeCardSize)); |
| 162 | + return sizes.reduce((a, b) => a + b, 0); |
| 163 | + } |
| 164 | + |
| 165 | + static async getConfigElement() { |
| 166 | + // Ensure the hui-stack-card-editor is loaded. |
| 167 | + let cls = customElements.get('hui-vertical-stack-card'); |
| 168 | + if (!cls) { |
| 169 | + const helpers = await window.loadCardHelpers(); |
| 170 | + helpers.createCardElement({ type: 'vertical-stack', cards: [] }); |
| 171 | + await customElements.whenDefined('hui-vertical-stack-card'); |
| 172 | + cls = customElements.get('hui-vertical-stack-card'); |
| 173 | + } |
| 174 | + const configElement = await cls.getConfigElement(); |
| 175 | + |
| 176 | + // Patch setConfig to remove non-VSIC config options. |
| 177 | + const originalSetConfig = configElement.setConfig; |
| 178 | + configElement.setConfig = (config) => |
| 179 | + originalSetConfig.call(configElement, { |
| 180 | + type: config.type, |
| 181 | + title: config.title, |
| 182 | + cards: config.cards || [], |
| 183 | + }); |
| 184 | + |
| 185 | + return configElement; |
| 186 | + } |
| 187 | + |
| 188 | + static getStubConfig() { |
| 189 | + return { |
| 190 | + cards: [], |
| 191 | + }; |
| 192 | + } |
| 193 | +} |
| 194 | + |
| 195 | +customElements.define('vertical-stack-in-card', VerticalStackInCard); |
| 196 | +window.customCards = window.customCards || []; |
| 197 | +window.customCards.push({ |
| 198 | + type: 'vertical-stack-in-card', |
| 199 | + name: 'Vertical Stack In Card', |
| 200 | + description: 'Group multiple cards into a single sleek card.', |
| 201 | + preview: false, |
| 202 | + documentationURL: 'https://github.com/ofekashery/vertical-stack-in-card', |
| 203 | +}); |
0 commit comments