初始化config文件
This commit is contained in:
@@ -0,0 +1,323 @@
|
||||
console.log(
|
||||
`%cvertical-stack-in-card\n%cVersion: ${'1.1.2'}`,
|
||||
'color: #1976d2; font-weight: bold;',
|
||||
'',
|
||||
);
|
||||
|
||||
class VerticalStackInCardEditor extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this._config = {};
|
||||
this._initialized = false;
|
||||
}
|
||||
|
||||
async setConfig(config) {
|
||||
this._config = config;
|
||||
if (!this._initialized) {
|
||||
await this._build();
|
||||
} else {
|
||||
this._sync();
|
||||
}
|
||||
}
|
||||
|
||||
async _build() {
|
||||
this._initialized = true;
|
||||
|
||||
// Load the hui-vertical-stack-card editor for title + cards UI.
|
||||
let cls = customElements.get('hui-vertical-stack-card');
|
||||
if (!cls) {
|
||||
const helpers = await window.loadCardHelpers();
|
||||
helpers.createCardElement({ type: 'vertical-stack', cards: [] });
|
||||
await customElements.whenDefined('hui-vertical-stack-card');
|
||||
cls = customElements.get('hui-vertical-stack-card');
|
||||
}
|
||||
this._huiEditor = await cls.getConfigElement();
|
||||
this._huiEditor.addEventListener('config-changed', (ev) => {
|
||||
// Only intercept the final top-level event from the hui editor itself.
|
||||
// Intermediate events (from child card editors, the card picker, etc.)
|
||||
// must propagate so HA's internals keep working.
|
||||
if (ev.detail.config.type !== 'custom:vertical-stack-in-card') return;
|
||||
ev.stopPropagation();
|
||||
this._fireConfigChanged({ ...this._config, ...ev.detail.config });
|
||||
});
|
||||
|
||||
// Horizontal toggle row.
|
||||
const switchContainer = document.createElement('div');
|
||||
switchContainer.style.cssText =
|
||||
'display:flex;align-items:center;justify-content:space-between;padding:8px 0;';
|
||||
const label = document.createElement('span');
|
||||
label.textContent = 'Stack horizontally';
|
||||
this._horizontalSwitch = document.createElement('ha-switch');
|
||||
this._horizontalSwitch.addEventListener('change', () => {
|
||||
const config = { ...this._config };
|
||||
if (this._horizontalSwitch.checked) {
|
||||
config.horizontal = true;
|
||||
} else {
|
||||
delete config.horizontal;
|
||||
}
|
||||
this._fireConfigChanged(config);
|
||||
});
|
||||
switchContainer.appendChild(label);
|
||||
switchContainer.appendChild(this._horizontalSwitch);
|
||||
this.appendChild(switchContainer);
|
||||
this.appendChild(this._huiEditor);
|
||||
|
||||
this._sync();
|
||||
}
|
||||
|
||||
set hass(hass) {
|
||||
this._hass = hass;
|
||||
if (this._huiEditor) this._huiEditor.hass = hass;
|
||||
}
|
||||
|
||||
// HA sets lovelace on the config element — forward it into the hui editor
|
||||
// so hui-cards-editor and hui-card-picker receive it and work correctly.
|
||||
set lovelace(lovelace) {
|
||||
this._lovelace = lovelace;
|
||||
if (this._huiEditor) this._huiEditor.lovelace = lovelace;
|
||||
}
|
||||
|
||||
_sync() {
|
||||
if (this._huiEditor) {
|
||||
if (this._hass) this._huiEditor.hass = this._hass;
|
||||
if (this._lovelace) this._huiEditor.lovelace = this._lovelace;
|
||||
this._huiEditor.setConfig({
|
||||
type: this._config.type,
|
||||
title: this._config.title,
|
||||
cards: this._config.cards || [],
|
||||
});
|
||||
}
|
||||
if (this._horizontalSwitch) {
|
||||
this._horizontalSwitch.checked = !!this._config.horizontal;
|
||||
}
|
||||
}
|
||||
|
||||
_fireConfigChanged(config) {
|
||||
this._config = config;
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('config-changed', {
|
||||
detail: { config },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(
|
||||
'vertical-stack-in-card-editor',
|
||||
VerticalStackInCardEditor,
|
||||
);
|
||||
|
||||
class VerticalStackInCard extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
get updateComplete() {
|
||||
return this._cardSize.promise;
|
||||
}
|
||||
|
||||
setConfig(config) {
|
||||
this._cardSize = {};
|
||||
this._cardSize.promise = new Promise(
|
||||
(resolve) => (this._cardSize.resolve = resolve),
|
||||
);
|
||||
|
||||
if (!config || !config.cards || !Array.isArray(config.cards)) {
|
||||
throw new Error('Card config incorrect');
|
||||
}
|
||||
this._config = config;
|
||||
this._refCards = [];
|
||||
this.renderCard();
|
||||
}
|
||||
|
||||
async renderCard() {
|
||||
const config = this._config;
|
||||
const promises = config.cards.map((config) =>
|
||||
this._createCardElement(config),
|
||||
);
|
||||
this._refCards = await Promise.all(promises);
|
||||
|
||||
// Style cards
|
||||
this._refCards.forEach((card) => {
|
||||
if (card.updateComplete) {
|
||||
card.updateComplete.then(() => this._styleCard(card));
|
||||
} else {
|
||||
this._styleCard(card);
|
||||
}
|
||||
});
|
||||
|
||||
// Create the card
|
||||
const card = document.createElement('ha-card');
|
||||
const cardContent = document.createElement('div');
|
||||
card.header = config.title;
|
||||
card.style.overflow = 'visible';
|
||||
cardContent.style.display = 'flex';
|
||||
// height: 100% is only appropriate for horizontal stacks, where the card
|
||||
// should fill the grid-allocated row height. For vertical stacks, height
|
||||
// must be content-driven (auto) — otherwise child cards that also set
|
||||
// height: 100% (e.g. hui-horizontal-stack-card after HA 2025.x) will
|
||||
// resolve their 100% against our container height and inflate to fill it.
|
||||
if (config.horizontal) {
|
||||
card.style.height = '100%';
|
||||
cardContent.style.height = '100%';
|
||||
}
|
||||
|
||||
this._refCards.forEach((refCard) => cardContent.appendChild(refCard));
|
||||
|
||||
if (config.horizontal) {
|
||||
// Proportional widths from grid_options.columns (native stack gives equal
|
||||
// flex: 1 1 0 to all; we use columns as relative flex weights).
|
||||
this._refCards.forEach((refCard, i) => {
|
||||
const cols = config.cards[i]?.grid_options?.columns ?? 1;
|
||||
refCard.style.flex = `${cols} ${cols} 0`;
|
||||
refCard.style.minWidth = '0';
|
||||
});
|
||||
} else {
|
||||
cardContent.style.flexDirection = 'column';
|
||||
// Proportional heights from grid_options.rows; cards without rows are
|
||||
// content-sized (flex: 0 0 auto).
|
||||
this._refCards.forEach((refCard, i) => {
|
||||
const rows = config.cards[i]?.grid_options?.rows;
|
||||
refCard.style.flex = rows ? `${rows} ${rows} 0` : '0 0 auto';
|
||||
});
|
||||
}
|
||||
|
||||
card.appendChild(cardContent);
|
||||
|
||||
const shadowRoot = this.shadowRoot || this.attachShadow({ mode: 'open' });
|
||||
while (shadowRoot.hasChildNodes()) {
|
||||
shadowRoot.removeChild(shadowRoot.lastChild);
|
||||
}
|
||||
shadowRoot.appendChild(card);
|
||||
|
||||
// Calculate card size
|
||||
this._cardSize.resolve();
|
||||
}
|
||||
|
||||
async _createCardElement(cardConfig) {
|
||||
const helpers = await window.loadCardHelpers();
|
||||
const element =
|
||||
cardConfig.type === 'divider'
|
||||
? helpers.createRowElement(cardConfig)
|
||||
: helpers.createCardElement(cardConfig);
|
||||
|
||||
element.hass = this._hass;
|
||||
element.addEventListener(
|
||||
'll-rebuild',
|
||||
(ev) => {
|
||||
ev.stopPropagation();
|
||||
this._createCardElement(cardConfig).then(() => {
|
||||
this.renderCard();
|
||||
});
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
return element;
|
||||
}
|
||||
|
||||
set hass(hass) {
|
||||
this._hass = hass;
|
||||
if (this._refCards) {
|
||||
this._refCards.forEach((card) => {
|
||||
card.hass = hass;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_styleCard(element) {
|
||||
const config = this._config;
|
||||
if (element.shadowRoot) {
|
||||
if (element.shadowRoot.querySelector('ha-card')) {
|
||||
let ele = element.shadowRoot.querySelector('ha-card');
|
||||
ele.style.boxShadow = 'none';
|
||||
ele.style.border = 'none';
|
||||
if ('styles' in config) {
|
||||
Object.entries(config.styles).forEach(([key, value]) =>
|
||||
ele.style.setProperty(key, value),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let searchEles = element.shadowRoot.getElementById('root');
|
||||
if (!searchEles) {
|
||||
searchEles = element.shadowRoot.getElementById('card');
|
||||
}
|
||||
if (!searchEles) return;
|
||||
searchEles = searchEles.childNodes;
|
||||
for (let i = 0; i < searchEles.length; i++) {
|
||||
if (searchEles[i].style) {
|
||||
searchEles[i].style.margin = '0px';
|
||||
}
|
||||
this._styleCard(searchEles[i]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
typeof element.querySelector === 'function' &&
|
||||
element.querySelector('ha-card')
|
||||
) {
|
||||
let ele = element.querySelector('ha-card');
|
||||
ele.style.boxShadow = 'none';
|
||||
ele.style.borderRadius = '0';
|
||||
ele.style.border = 'none';
|
||||
if ('styles' in config) {
|
||||
Object.entries(config.styles).forEach(([key, value]) =>
|
||||
ele.style.setProperty(key, value),
|
||||
);
|
||||
}
|
||||
}
|
||||
let searchEles = element.childNodes;
|
||||
for (let i = 0; i < searchEles.length; i++) {
|
||||
if (searchEles[i] && searchEles[i].style) {
|
||||
searchEles[i].style.margin = '0px';
|
||||
}
|
||||
this._styleCard(searchEles[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_computeCardSize(card) {
|
||||
if (typeof card.getCardSize === 'function') {
|
||||
return card.getCardSize();
|
||||
}
|
||||
return customElements
|
||||
.whenDefined(card.localName)
|
||||
.then(() => this._computeCardSize(card))
|
||||
.catch(() => 1);
|
||||
}
|
||||
|
||||
async getCardSize() {
|
||||
await this._cardSize.promise;
|
||||
const sizes = await Promise.all(this._refCards.map(this._computeCardSize));
|
||||
return sizes.reduce((a, b) => a + b, 0);
|
||||
}
|
||||
|
||||
getGridOptions() {
|
||||
return {
|
||||
columns: 12,
|
||||
rows: 'auto',
|
||||
min_columns: 3,
|
||||
};
|
||||
}
|
||||
|
||||
static getConfigElement() {
|
||||
return document.createElement('vertical-stack-in-card-editor');
|
||||
}
|
||||
|
||||
static getStubConfig() {
|
||||
return {
|
||||
cards: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('vertical-stack-in-card', VerticalStackInCard);
|
||||
window.customCards = window.customCards || [];
|
||||
window.customCards.push({
|
||||
type: 'vertical-stack-in-card',
|
||||
name: 'Vertical Stack In Card',
|
||||
description: 'Group multiple cards into a single sleek card.',
|
||||
preview: false,
|
||||
documentationURL: 'https://github.com/ofekashery/vertical-stack-in-card',
|
||||
});
|
||||
Binary file not shown.
Reference in New Issue
Block a user