From 1e737f609a636a88fdc8fc7ef50a14d2fc6940e8 Mon Sep 17 00:00:00 2001 From: Cloos <36499953+Clooos@users.noreply.github.com> Date: Thu, 5 Oct 2023 14:44:40 +0200 Subject: [PATCH] =?UTF-8?q?v0.0.1=20-=20Bubble=20Card=20is=20out=20of=20be?= =?UTF-8?q?ta=20!=20=F0=9F=A4=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/bubble-card.js | 423 +++++++++++++++++++++++++++------------------ 1 file changed, 259 insertions(+), 164 deletions(-) diff --git a/src/bubble-card.js b/src/bubble-card.js index 8e71715..1a6b1ef 100644 --- a/src/bubble-card.js +++ b/src/bubble-card.js @@ -1,4 +1,6 @@ -var version = 'v0.0.1-beta.11'; +var version = 'v1.0.0'; + +let editor; class BubbleCard extends HTMLElement { constructor() { @@ -6,6 +8,8 @@ class BubbleCard extends HTMLElement { if (!window.eventAdded) { // 'urlChanged' custom event const pushState = history.pushState; + window.popUpIntialized = false; + history.pushState = function () { pushState.apply(history, arguments); window.dispatchEvent(new Event('pushstate')); @@ -17,14 +21,14 @@ class BubbleCard extends HTMLElement { window.dispatchEvent(new Event('replacestate')); }; - ['popstate', 'pushstate', 'replacestate', 'mousedown', 'touchstart'].forEach((eventType) => { + ['pushstate', 'replacestate', 'mousedown', 'touchstart'].forEach((eventType) => { window.addEventListener(eventType, urlChanged); }); const event = new Event('urlChanged'); function urlChanged() { - const newUrl = window.location.href; + const newUrl = window.location.href; if (newUrl !== this.currentUrl) { window.dispatchEvent(event); this.currentUrl = newUrl; @@ -34,6 +38,8 @@ class BubbleCard extends HTMLElement { // Check url when pop-ups are initialized const popUpInitialized = () => { window.dispatchEvent(event); + window.addEventListener('popstate', urlChanged); + console.log(event); setTimeout(() => { window.removeEventListener('popUpInitialized', popUpInitialized); }, 1000); @@ -60,12 +66,14 @@ class BubbleCard extends HTMLElement { this.card = this.shadowRoot.querySelector("ha-card"); this.content = this.shadowRoot.querySelector("div"); } - - if (window.location.search !== "?edit=1") { - this.editor = false; - } else { - this.editor = true; - } + + let editorElement = document.querySelector("body > home-assistant") + .shadowRoot.querySelector("home-assistant-main") + .shadowRoot.querySelector("ha-drawer > partial-panel-resolver > ha-panel-lovelace") + .shadowRoot.querySelector("hui-root") + .shadowRoot.querySelector("div"); + + editor = editorElement.classList.contains('edit-mode'); function toggleEntity(entityId) { hass.callService('homeassistant', 'toggle', { @@ -234,8 +242,7 @@ class BubbleCard extends HTMLElement { addAction('contextmenu', 'hold', element, self); } - const customStyles = !this.config.styles ? '' : this.config.styles; - + let customStyles = !this.config.styles ? '' : this.config.styles; let entityId = this.config.entity ? this.config.entity : ''; let icon = !this.config.icon && this.config.entity ? hass.states[entityId].attributes.icon || hass.states[entityId].attributes.entity_picture || '' : this.config.icon || ''; let name = this.config.name ? this.config.name : this.config.entity ? hass.states[entityId].attributes.friendly_name : ''; @@ -244,25 +251,44 @@ class BubbleCard extends HTMLElement { let isSidebarHidden = this.config.is_sidebar_hidden || false; let state = entityId ? hass.states[entityId].state : ''; let formatedState; - let autoClose = this.config.autoclose || false; + let autoClose = this.config.auto_close || false; + let marginCenter = this.config.margin + ? (this.config.margin !== '0' ? this.config.margin : '0px') + : '7px'; + let backOpen = this.config.back_open; + let popUpOpen; switch (this.config.card_type) { // Initialize pop-up card case 'pop-up': if (!this.getRootNode().host) { // Hide vertical stack content before initialization - if (this.editor !== true) { + if (editor !== true) { this.card.style.marginTop = '2000px'; } } else { + const popUpHash = this.config.hash; if (!this.popUp) { this.card.style.marginTop = '0'; this.popUp = this.getRootNode().querySelector('#root'); - const event = new Event('popUpInitialized'); - window.dispatchEvent(event); + + if (!window.popUpIntialized) { + console.log("Back open : ", backOpen); + + if (backOpen) { + window.backOpen = true; + const event = new Event('popUpInitialized'); + setTimeout(() => { + window.dispatchEvent(event); + }, 10); + } else { // + window.backOpen = false; + popUpOpen = popUpHash + false; + history.replaceState(null, null, location.href.split('#')[0]); + } + window.popUpIntialized = true; + } } - - const popUpHash = this.config.hash; const popUp = this.popUp; const text = this.config.text || ''; const triggerEntity = this.config.trigger_entity ? this.config.trigger_entity : ''; @@ -277,7 +303,6 @@ class BubbleCard extends HTMLElement { : '0px'; const displayPowerButton = this.config.entity ? 'flex' : 'none'; state = this.config.state ? hass.states[this.config.state].state : ''; - let closeTimeout; if (!this.headerAdded) { @@ -321,7 +346,7 @@ class BubbleCard extends HTMLElement { const button = document.createElement("button"); button.setAttribute("class", "close-pop-up"); - button.onclick = function() { history.replaceState(null, null, location.href.split('#')[0]); localStorage.setItem('isManuallyClosed_' + popUpHash, true); }; + button.onclick = function() { closePopUp(); localStorage.setItem('isManuallyClosed_' + popUpHash, true); }; headerContainer.appendChild(button); const haIcon3 = document.createElement("ha-icon"); @@ -367,13 +392,14 @@ class BubbleCard extends HTMLElement { toggleEntity(entityId); }); - window.addEventListener('mousedown', function(e) { + window.addEventListener('click', function(e) { //reset auto close location.hash === popUpHash && resetAutoClose(); if (location.hash === popUpHash && !e.composedPath().some(el => el.nodeName === 'HA-MORE-INFO-DIALOG') && !e.composedPath().some(el => el.id === 'root' && !el.classList.contains('close-pop-up'))) { + popUpOpen = popUpHash + false; history.replaceState(null, null, location.href.split('#')[0]); localStorage.setItem('isManuallyClosed_' + popUpHash, true) } @@ -381,6 +407,7 @@ class BubbleCard extends HTMLElement { window.addEventListener('keydown', function(event) { if (event.key === 'Escape') { + popUpOpen = popUpHash + false; history.replaceState(null, null, location.href.split('#')[0]); localStorage.setItem('isManuallyClosed_' + popUpHash, true) } @@ -392,12 +419,12 @@ class BubbleCard extends HTMLElement { let lastTouchY; popUp.addEventListener('touchstart', function(event) { + //reset auto close + location.hash === popUpHash && resetAutoClose(); + // Record the Y position of the finger at the start of the touch startTouchY = event.touches[0].clientY; lastTouchY = startTouchY; - - //reset auto close - location.hash === popUpHash && resetAutoClose(); }); popUp.addEventListener('touchmove', function(event) { @@ -406,7 +433,9 @@ class BubbleCard extends HTMLElement { // If the distance is positive (i.e., the finger is moving downward) and exceeds a certain threshold, close the pop-up if (touchMoveDistance > 300 && event.touches[0].clientY > lastTouchY) { + popUpOpen = popUpHash + false; history.replaceState(null, null, location.href.split('#')[0]); + popUpOpen = popUpHash + false; localStorage.setItem('isManuallyClosed_' + popUpHash, true) } @@ -418,6 +447,16 @@ class BubbleCard extends HTMLElement { } if (triggerEntity) { + if (localStorage.getItem('previousTriggerState_' + popUpHash) === null) { + localStorage.setItem('previousTriggerState_' + popUpHash, ''); + } + if (localStorage.getItem('isManuallyClosed_' + popUpHash) === null) { + localStorage.setItem('isManuallyClosed_' + popUpHash, 'false'); + } + if (localStorage.getItem('isTriggered_' + popUpHash) === null) { + localStorage.setItem('isTriggered_' + popUpHash, 'false'); + } + let previousTriggerState = localStorage.getItem('previousTriggerState_' + popUpHash); let isManuallyClosed = localStorage.getItem('isManuallyClosed_' + popUpHash) === 'true'; let isTriggered = localStorage.getItem('isTriggered_' + popUpHash) === 'true'; @@ -440,6 +479,7 @@ class BubbleCard extends HTMLElement { localStorage.setItem('isTriggered_' + popUpHash, isTriggered); } else if (hass.states[triggerEntity].state !== triggerState && triggerClose && popUp.classList.contains('open-pop-up') && isTriggered && !isManuallyClosed) { history.replaceState(null, null, location.href.split('#')[0]); + popUpOpen = popUpHash + false; isTriggered = false; isManuallyClosed = true; localStorage.setItem('isManuallyClosed_' + popUpHash, isManuallyClosed); @@ -458,7 +498,7 @@ class BubbleCard extends HTMLElement { } function checkHash() { - if (!this.editor) { + if (!editor) { let hash = location.hash.split('?')[0]; // Open on hash change @@ -474,14 +514,17 @@ class BubbleCard extends HTMLElement { function openPopUp() { popUp.classList.remove('close-pop-up'); popUp.classList.add('open-pop-up'); + popUpOpen = popUpHash + true; resetAutoClose(); } function closePopUp() { popUp.classList.remove('open-pop-up'); popUp.classList.add('close-pop-up'); + popUpOpen = popUpHash + false; clearTimeout(closeTimeout); } + function resetAutoClose() { //Clear any existing timeout clearTimeout(closeTimeout); @@ -490,9 +533,9 @@ class BubbleCard extends HTMLElement { closeTimeout = setTimeout(autoClosePopUp, autoClose); } } - + function autoClosePopUp(){ - history.replaceState(null, null, location.href.split('#')[0]); + closePopUp(); } const popUpStyles = ` @@ -507,7 +550,7 @@ class BubbleCard extends HTMLElement { } #root { position: fixed !important; - margin: 0 -7px; + margin: 0 -${marginCenter}; /* 7px */ width: 100%; background-color: var(--ha-card-background,var(--card-background-color)); border-radius: 42px; @@ -661,7 +704,7 @@ class BubbleCard extends HTMLElement { addStyles(this, popUpStyles, customStyles, state, entityId, '', popUp); addStyles(this, headerStyles, customStyles, state, entityId); - if (this.editor === true) { + if (editor === true) { popUp.classList.add('editor'); popUp.classList.remove('open-pop-up'); popUp.classList.remove('close-pop-up'); @@ -675,14 +718,34 @@ class BubbleCard extends HTMLElement { case 'horizontal-buttons-stack' : const createButton = (button, link, icon) => { const buttonElement = document.createElement("button"); - buttonElement.onclick = function() { navigate('', link); }; - buttonElement.setAttribute("class", "button"); + buttonElement.setAttribute("class", `button ${link.substring(1)}`); buttonElement.innerHTML = ` ${icon !== '' ? `` : ''} ${button !== '' ? `

${button}

` : ''} `; + + buttonElement.addEventListener('click', (event) => { + event.stopPropagation(); + popUpOpen = location.hash + true; + const manuallyClosed = localStorage.getItem('isManuallyClosed_' + link) === 'true'; + if (popUpOpen !== link + true) { + navigate('', link); + popUpOpen = link + true; + } else { + history.replaceState(null, null, location.href.split('#')[0]); + popUpOpen = link + false; + } + }); + return buttonElement; }; + + if (!this.buttonsAdded) { + const buttonsContainer = document.createElement("div"); + buttonsContainer.setAttribute("class", "horizontal-buttons-stack-container"); + this.content.appendChild(buttonsContainer); + this.buttonsContainer = buttonsContainer; + } const updateButtonStyle = (buttonElement, lightEntity) => { if (hass.states[lightEntity].attributes.rgb_color) { @@ -698,129 +761,118 @@ class BubbleCard extends HTMLElement { buttonElement.style.border = '1px solid var(--primary-text-color)'; } }; - - if (!this.buttonsAdded) { - const buttonsContainer = document.createElement("div"); - buttonsContainer.setAttribute("class", "horizontal-buttons-stack-container"); - this.content.appendChild(buttonsContainer); - this.buttonsContainer = buttonsContainer; + + let buttonsList = []; + let i = 1; + while (this.config[i + '_link']) { + const prefix = i + '_'; + const button = this.config[prefix + 'name'] || ''; + const pirSensor = this.config[prefix + 'pir_sensor']; + icon = this.config[prefix + 'icon'] || ''; + const link = this.config[prefix + 'link']; + const lightEntity = this.config[prefix + 'entity']; + buttonsList.push({ + button, + pirSensor, + icon, + link, + lightEntity + }); + i++; } - - const updateButtonsOrder = () => { - let buttonsList = []; - let i = 1; - while (this.config[i + '_link']) { - const prefix = i + '_'; - const button = this.config[prefix + 'name'] || ''; - const pirSensor = this.config[prefix + 'pir_sensor']; - icon = this.config[prefix + 'icon'] || ''; - const link = this.config[prefix + 'link']; - const lightEntity = this.config[prefix + 'entity']; - buttonsList.push({ - button, - pirSensor, - icon, - link, - lightEntity - }); - i++; - } - - if (this.config.auto_order) { - buttonsList.sort((a, b) => { - // Check if both PIR sensors are defined - if (a.pirSensor && b.pirSensor) { - // Check if the PIR sensor state is "on" for both buttons - if (hass.states[a.pirSensor].state === "on" && hass.states[b.pirSensor].state === "on") { - let aTime = hass.states[a.pirSensor].last_updated; - let bTime = hass.states[b.pirSensor].last_updated; - return aTime < bTime ? 1 : -1; - } - // If only a.pirSensor is "on", place a before b - else if (hass.states[a.pirSensor].state === "on") { - return -1; - } - // If only b.pirSensor is "on", place b before a - else if (hass.states[b.pirSensor].state === "on") { - return 1; - } - // If neither PIR sensor is "on", arrangement based only on the state of last updated even if off + + if (this.config.auto_order) { + buttonsList.sort((a, b) => { + // Check if both PIR sensors are defined + if (a.pirSensor && b.pirSensor) { + // Check if the PIR sensor state is "on" for both buttons + if (hass.states[a.pirSensor].state === "on" && hass.states[b.pirSensor].state === "on") { let aTime = hass.states[a.pirSensor].last_updated; let bTime = hass.states[b.pirSensor].last_updated; return aTime < bTime ? 1 : -1; } - // If a.pirSensor is not defined, place a after b - else if (!a.pirSensor) { - return 1; - } - // If b.pirSensor is not defined, place b after a - else if (!b.pirSensor) { + // If only a.pirSensor is "on", place a before b + else if (hass.states[a.pirSensor].state === "on") { return -1; } - }); - } - - if (!this.buttonsAdded || this.editor) { - this.card.classList.add('horizontal-buttons-stack'); - - // Fix for editor mode - if (this.editor && this.buttonsContainer) { - while (this.buttonsContainer.firstChild) { - this.buttonsContainer.removeChild(this.buttonsContainer.firstChild); + // If only b.pirSensor is "on", place b before a + else if (hass.states[b.pirSensor].state === "on") { + return 1; } - localStorage.setItem('editorMode', true); - } else { - localStorage.setItem('editorMode', false); + // If neither PIR sensor is "on", arrangement based only on the state of last updated even if off + let aTime = hass.states[a.pirSensor].last_updated; + let bTime = hass.states[b.pirSensor].last_updated; + return aTime < bTime ? 1 : -1; } - // End of fix - - const buttons = {}; - buttonsList.forEach(button => { - const buttonElement = createButton(button.button, button.link, button.icon); - // Store the button element using its link as key - buttons[button.link] = buttonElement; - this.buttonsContainer.appendChild(buttonElement); - }); - this.buttonsAdded = true; - this.buttons = buttons; - } - - if (this.editor) { - localStorage.setItem('justExitedEditor', false); - } else if (localStorage.getItem('justExitedEditor') === 'false') { - localStorage.setItem('justExitedEditor', true); - } - - let currentPosition = 0; - let margin = 12; - const justExitedEditor = localStorage.getItem('editorMode') === 'true' && !this.editor; - - buttonsList.forEach((button, index) => { - let buttonElement = this.buttons[button.link]; - if (buttonElement) { - let buttonWidth = localStorage.getItem(`buttonWidth-${button.link}`); - let buttonContent = localStorage.getItem(`buttonContent-${button.link}`); - if (!buttonWidth || buttonWidth === '0' || buttonContent !== buttonElement.innerHTML || this.editor || justExitedEditor) { - buttonWidth = buttonElement.offsetWidth; - localStorage.setItem(`buttonWidth-${button.link}`, buttonWidth); - localStorage.setItem(`buttonContent-${button.link}`, buttonElement.innerHTML); - if (this.editor) { - margin = 36; // Recalculate margin for editor mode - } else if (justExitedEditor) { - margin = 12; // Recalculate margin for regular mode - } - } - buttonElement.style.transform = `translateX(${currentPosition}px)`; - currentPosition += parseInt(buttonWidth) + margin; + // If a.pirSensor is not defined, place a after b + else if (!a.pirSensor) { + return 1; } - if (button.lightEntity) { - updateButtonStyle(buttonElement, button.lightEntity); + // If b.pirSensor is not defined, place b after a + else if (!b.pirSensor) { + return -1; } }); } - - updateButtonsOrder(); - + + if (!this.buttonsAdded || editor) { + this.card.classList.add('horizontal-buttons-stack'); + + // Fix for editor mode + if (editor && this.buttonsContainer) { + while (this.buttonsContainer.firstChild) { + this.buttonsContainer.removeChild(this.buttonsContainer.firstChild); + } + localStorage.setItem('editorMode', true); + } else { + localStorage.setItem('editorMode', false); + } + // End of fix + + const buttons = {}; + buttonsList.forEach(button => { + const buttonElement = createButton(button.button, button.link, button.icon); + // Store the button element using its link as key + buttons[button.link] = buttonElement; + this.buttonsContainer.appendChild(buttonElement); + }); + this.buttonsAdded = true; + this.buttons = buttons; + } + + if (editor) { + localStorage.setItem('justExitedEditor', false); + } else if (localStorage.getItem('justExitedEditor') === 'false') { + localStorage.setItem('justExitedEditor', true); + } + + let currentPosition = 0; + let margin = 12; + const justExitedEditor = localStorage.getItem('editorMode') === 'true' && !editor; + + buttonsList.forEach((button, index) => { + let buttonElement = this.buttons[button.link]; + if (buttonElement) { + let buttonWidth = localStorage.getItem(`buttonWidth-${button.link}`); + let buttonContent = localStorage.getItem(`buttonContent-${button.link}`); + if (!buttonWidth || buttonWidth === '0' || buttonContent !== buttonElement.innerHTML || editor || justExitedEditor) { + buttonWidth = buttonElement.offsetWidth; + localStorage.setItem(`buttonWidth-${button.link}`, buttonWidth); + localStorage.setItem(`buttonContent-${button.link}`, buttonElement.innerHTML); + if (editor) { + margin = 36; // Recalculate margin for editor mode + } else if (justExitedEditor) { + margin = 12; // Recalculate margin for regular mode + } + } + buttonElement.style.transform = `translateX(${currentPosition}px)`; + currentPosition += parseInt(buttonWidth) + margin; + } + if (button.lightEntity) { + updateButtonStyle(buttonElement, button.lightEntity); + } + }); + const horizontalButtonsStackStyles = ` ha-card { border-radius: 0; @@ -832,7 +884,7 @@ class BubbleCard extends HTMLElement { position: fixed; height: 51px; bottom: 16px; - left: 7px; + left: ${marginCenter}; /* transform: translateY(200px); */ /* animation: from-bottom 1.3s forwards; */ z-index: 1 !important; /* Higher value hide the more-info panel */ @@ -854,7 +906,7 @@ class BubbleCard extends HTMLElement { .button { display: flex; position: absolute; - /* box-sizing: border-box; */ + box-sizing: border-box !important; border: 1px solid var(--primary-text-color); align-items: center; height: 50px; @@ -872,7 +924,7 @@ class BubbleCard extends HTMLElement { } .card-content { width: calc(100% + 18px); - box-sizing: border-box; + box-sizing: border-box !important; margin: 0 -36px !important; padding: 0 36px !important; overflow: scroll !important; @@ -948,13 +1000,14 @@ class BubbleCard extends HTMLElement { addStyles(this, horizontalButtonsStackStyles, customStyles); - if (this.editor) { + if (editor) { this.buttonsContainer.classList.add('editor'); this.card.classList.add('editor'); } else { this.buttonsContainer.classList.remove('editor'); this.card.classList.remove('editor'); } + break; // Initialize button @@ -1001,9 +1054,9 @@ class BubbleCard extends HTMLElement { rangeFill.style.transform = `translateX(${currentVolume * 100}%)`; } - if (!this.buttonContainer || this.editor) { + if (!this.buttonContainer || editor) { // Fix for editor mode - if (this.editor && this.buttonContainer) { + if (editor && this.buttonContainer) { while (this.buttonContainer.firstChild) { this.buttonContainer.removeChild(this.buttonContainer.firstChild); } @@ -1013,13 +1066,13 @@ class BubbleCard extends HTMLElement { this.buttonContainer = this.content.querySelector(".button-container"); - if (buttonType === 'slider' && (!this.buttonAdded || this.editor)) { + if (buttonType === 'slider' && (!this.buttonAdded || editor)) { this.buttonContainer.appendChild(rangeSlider); rangeSlider.appendChild(iconContainer); rangeSlider.appendChild(nameContainer); rangeSlider.appendChild(rangeFill); this.rangeFill = this.content.querySelector(".range-fill"); - } else if (buttonType === 'switch' || buttonType === 'custom' || this.editor) { + } else if (buttonType === 'switch' || buttonType === 'custom' || editor) { this.buttonContainer.appendChild(switchButton); switchButton.appendChild(iconContainer); switchButton.appendChild(nameContainer); @@ -1307,9 +1360,9 @@ class BubbleCard extends HTMLElement { // Initialize separator case 'separator' : - if (!this.separatorAdded || this.editor) { + if (!this.separatorAdded || editor) { // Fix for editor mode - if (this.editor && this.separatorContainer) { + if (editor && this.separatorContainer) { while (this.separatorContainer.firstChild) { this.separatorContainer.removeChild(this.separatorContainer.firstChild); } @@ -1380,9 +1433,9 @@ class BubbleCard extends HTMLElement { formatedState = this.config.entity ? hass.formatEntityState(hass.states[this.config.entity]) : ''; - if (!this.coverAdded || this.editor) { + if (!this.coverAdded || editor) { // Fix for editor mode - if (this.editor && this.coverContainer) { + if (editor && this.coverContainer) { while (this.coverContainer.firstChild) { this.coverContainer.removeChild(this.coverContainer.firstChild); } @@ -1537,7 +1590,7 @@ class BubbleCard extends HTMLElement { setConfig(config) { if (config.card_type === 'pop-up') { if (!config.hash) { - throw new Error("You need to define an hash"); + throw new Error("You need to define an hash. Please note that this card must be placed inside a vertical_stack to work as a pop-up."); } } else if (config.card_type === 'horizontal-buttons-stack') { var definedLinks = {}; @@ -1662,6 +1715,10 @@ class BubbleCardEditor extends LitElement { get _trigger_close() { return this._config.trigger_close || false; } + + get _margin() { + return this._config.margin || '7px'; + } get _margin_top_mobile() { return this._config.margin_top_mobile || '0px'; @@ -1678,6 +1735,14 @@ class BubbleCardEditor extends LitElement { get _is_sidebar_hidden() { return this._config.is_sidebar_hidden || false; } + + get _auto_close() { + return this._config.auto_close || ''; + } + + get _back_open() { + return this._config.back_open || false; + } get _icon_open() { return this._config.icon_open || ''; @@ -1694,11 +1759,7 @@ class BubbleCardEditor extends LitElement { get _close_service() { return this._config.open_service || 'cover.close_cover'; } - - get _autoclose() { - return this._config.autoclose || ''; - } - + get _stop_service() { return this._config.open_service || 'cover.stop_cover'; } @@ -1791,7 +1852,7 @@ class BubbleCardEditor extends LitElement {
${this.makeDropdown("Card type", "card_type", cardTypeList)}

Pop-up

- This card allows you to convert any vertical-stack into a pop-up. Each pop-up can be opened by targeting its link (e.g. '#pop-up-name'), with navigation_path or with the horizontal buttons stack that is included.

It must be placed within a vertical-stack card at the top most position to function properly. The pop-up will be hidden by default until you open it.
+ This card allows you to convert any vertical-stack into a pop-up. Each pop-up can be opened by targeting its link (e.g. '#pop-up-name'), with navigation_path or with the horizontal buttons stack that is included.

It must be placed within a vertical-stack card at the top most position to function properly. The pop-up will be hidden by default until you open it.

Styling options

+ - +
- +
+

Advanced settings

+ + +
+ +
+
+ Back button/event support : This allow you to navigate through your pop-ups when you press the back button of your browser. This setting can be applied only once, you don't need to change it in all pop-ups. Beta users: This was the previous default behavior, set it to true to keep everything like before the update. ${this.makeVersion()}
`; @@ -1977,6 +2058,13 @@ class BubbleCardEditor extends LitElement {

Styling options

+ ${this.makeDropdown("Card type", "card_type", cardTypeList)} -

Bubble Card

You need to add a card type first. + +

Almost everything is available in the GUI editor, but in the YAML editor you can add your own custom styles, create custom buttons or modify the tap actions of all cards. You can find more details on my GitHub page.

+ +

And if you like my project and want to support me, please consider making a donation. Any amount is welcome and very much appreciated! 🍻

+
+ + +
${this.makeVersion()} `;