diff --git a/README.md b/README.md
index f4d01e9..f55cd80 100644
--- a/README.md
+++ b/README.md
@@ -115,14 +115,17 @@ The first three are column based and work similarly:
All column based layouts accept the following `layout` options:
-| Option | Values | Description | Default |
-| ----------- | -------------- | ------------------------------------ | ------------------------------------------------- |
-| `width` | number | Size in pixels of each column | 300 |
-| `max_width` | number | Maximum width of a card | 1.5 \* `width` if specified
otherwise 500 |
-| `max_cols` | number | Maximum number of columns to show | 4 if sidebar is hidden
3 if sidebar is shown |
-| `rtl` | `true`/`false` | Place columns in right-to-left order | `false` |
-
-> NOTE: If you're migrating from layout-card "1.0" (v16 - sorry about the version number confusion), this is significantly fewer options than you are used to. \
+| Option | Values | Description | Default |
+| --------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
+| `width` | number | Size in pixels of each column | 300 |
+| `max_width` | number | Maximum width of a card | 1.5 \* `width` if specified
otherwise 500 |
+| `max_cols` | number | Maximum number of columns to show | 4 if sidebar is hidden
3 if sidebar is shown |
+| `rtl` | `true`/`false` | Place columns in right-to-left order | `false` |
+| `column_widths` | special | A [`grid-template-columns`](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns) specification of column widths | `none` |
+
+> NOTE: Even if `column_widths` is specified, the number of columns displayed will only depend on the available space, `width` and `max_cols`. If you get weird results, consider the Grid Layout.
+
+> NOTE 2: If you're migrating from layout-card "1.0" (v16 - sorry about the version number confusion), this is significantly fewer options than you are used to. \
> The reason for this is twofold. \
> First: Maintainability. As Home Assistant and Lovelace evolves, it grows increasingly more difficult to keep up the more options you want to keep alive. \
> Second: Usability. I want to focus on fewer options and doing the right thing out of the box instead. And the three remaining options actually have much more impact than you'd think.
@@ -176,13 +179,13 @@ The vertical layout accepts the following **card** `view_layout` options:
The grid layout will give you full controll of your cards by leveraging [CSS Grid](https://css-tricks.com/snippets/css/complete-guide-grid/).
-The grid layout accepts any option starting with `grid-` that works for a Grid Container.
+The grid layout accepts any option starting with `grid-` that works for a Grid Container as well as `grid` and `place-items`.
Furthermore, the special option `mediaquery` can be used to set grid options depending on currently matched [@media rules](https://www.w3schools.com/cssref/css3_pr_mediaquery.asp). This helps immensely in creating fully responsive layouts. \
Please see the example code accompanying the screen recording below. \
Note that only the first matching rule will be applied.
-For the card `view_layout` options. the grid layout accepts any css grid property starting with `grid-` that works for a Grid Item.
+For the card `view_layout` options. the grid layout accepts any css grid property starting with `grid-` that works for a Grid Item as well as `place-self`.
There's no point in me trying to list all `grid-` options here. Instead see the excellent guide linked above.
diff --git a/layout-card.js b/layout-card.js
index 5f0abb5..fbdc025 100644
--- a/layout-card.js
+++ b/layout-card.js
@@ -10,7 +10,7 @@ function t(t,e,i,s){var o,n=arguments.length,r=n<3?e:null===s?s=Object.getOwnPro
bottom: calc(16px + env(safe-area-inset-bottom));
z-index: 1;
}
- `}}t([J()],Et.prototype,"cards",void 0),t([J()],Et.prototype,"index",void 0),t([J()],Et.prototype,"narrow",void 0),t([J()],Et.prototype,"hass",void 0),t([J()],Et.prototype,"lovelace",void 0),t([J()],Et.prototype,"_editMode",void 0),t([J()],Et.prototype,"_config",void 0);class $t extends Et{constructor(){super(...arguments),this._mediaQueries=[]}async setConfig(t){var e,i,s;await super.setConfig(t);for(const t of this._config.cards)if("string"!=typeof(null===(e=t.view_layout)||void 0===e?void 0:e.show)&&(null===(s=null===(i=t.view_layout)||void 0===i?void 0:i.show)||void 0===s?void 0:s.mediaquery)){const e=window.matchMedia(`${t.view_layout.show.mediaquery}`);this._mediaQueries.push(e),e.addEventListener("change",(()=>this._makeLayout()))}else this._mediaQueries.push(null);this._observer&&this._observer.disconnect(),this._observer=new ut((()=>{this._updateSize()}))}async updated(t){var e;await super.updated(t),(t.has("_columns")||t.has("cards"))&&this._makeLayout(),t.has("_editMode")&&this._makeLayout(),t.has("narrow")&&this._updateSize(),t.has("hass")&&(null===(e=t.get("hass"))||void 0===e?void 0:e.dockedSidebar)!=this.hass.dockedSidebar&&this._updateSize()}async firstUpdated(){var t,e,i,s;this._updateSize();const o=(null===(t=this._config.layout)||void 0===t?void 0:t.max_width)||((null===(e=this._config.layout)||void 0===e?void 0:e.width)?Math.ceil(1.5*(null===(i=this._config.layout)||void 0===i?void 0:i.width)):500),n=(null===(s=this._config.layout)||void 0===s?void 0:s.width)?2*this._config.layout.width:600,r=document.createElement("style");r.innerHTML=`\n :host {\n --column-max-width: ${o}px;\n }\n @media (max-width: ${o}px) {\n .column:first-child > * {\n margin-left: 0;\n }\n .column:last-child > * {\n margin-right: 0;\n }\n }\n @media (max-width: ${n-1}px) {\n .column {\n --column-max-width: ${n}px;\n }\n }\n `,this.shadowRoot.appendChild(r)}connectedCallback(){super.connectedCallback(),this._updateSize()}disconnectedCallback(){super.disconnectedCallback(),this._observer.disconnect()}async _updateSize(){var t,e,i;let s=this.getBoundingClientRect().width,o=0;o=Math.floor(s/((null===(t=this._config.layout)||void 0===t?void 0:t.width)||300)),o=Math.min(o,(null===(e=this._config.layout)||void 0===e?void 0:e.max_cols)||("docked"===(null===(i=this.hass)||void 0===i?void 0:i.dockedSidebar)?3:4)),o=Math.max(o,1),o!==this._columns&&(this._columns=o)}_shouldShow(t,e,i){if(!super._shouldShow(t,e,i))return!1;const s=this._mediaQueries[i];return!s||!!s.matches}isBreak(t){return"layout-break"===t.localName}async _makeLayout(){this._makeColumnLayout()}async _makeColumnLayout(){var t;if(this._observer.disconnect(),!this._columns)return;let e=[];for(let t=0;t{const i=this._config.cards[e];return{card:t,config:i,index:e,show:this._shouldShow(t,i,e)}}));await this._placeColumnCards(e,i.filter((t=>{var e;return(null===(e=this.lovelace)||void 0===e?void 0:e.editMode)||t.show}))),e=e.filter((t=>t.childElementCount>0)),(null===(t=this._config.layout)||void 0===t?void 0:t.rtl)&&e.reverse();const s=this.shadowRoot.querySelector("#columns");for(;s.firstChild;)s.removeChild(s.firstChild);for(const t of e)s.appendChild(t);this.requestUpdate().then((()=>this._observer.observe(this)))}async _placeColumnCards(t,e){}render(){return j`
+ `}}t([J()],Et.prototype,"cards",void 0),t([J()],Et.prototype,"index",void 0),t([J()],Et.prototype,"narrow",void 0),t([J()],Et.prototype,"hass",void 0),t([J()],Et.prototype,"lovelace",void 0),t([J()],Et.prototype,"_editMode",void 0),t([J()],Et.prototype,"_config",void 0);class $t extends Et{constructor(){super(...arguments),this._mediaQueries=[]}async setConfig(t){var e,i,s;await super.setConfig(t);for(const t of this._config.cards)if("string"!=typeof(null===(e=t.view_layout)||void 0===e?void 0:e.show)&&(null===(s=null===(i=t.view_layout)||void 0===i?void 0:i.show)||void 0===s?void 0:s.mediaquery)){const e=window.matchMedia(`${t.view_layout.show.mediaquery}`);this._mediaQueries.push(e),e.addEventListener("change",(()=>this._makeLayout()))}else this._mediaQueries.push(null);this._observer&&this._observer.disconnect(),this._observer=new ut((()=>{this._updateSize()}))}async updated(t){var e;await super.updated(t),(t.has("_columns")||t.has("cards"))&&this._makeLayout(),t.has("_editMode")&&this._makeLayout(),t.has("narrow")&&this._updateSize(),t.has("hass")&&(null===(e=t.get("hass"))||void 0===e?void 0:e.dockedSidebar)!=this.hass.dockedSidebar&&this._updateSize()}async firstUpdated(){var t,e,i,s,o,n,r;this._updateSize();const a=(null===(t=this._config.layout)||void 0===t?void 0:t.width)||300,d=(null===(e=this._config.layout)||void 0===e?void 0:e.max_width)||((null===(i=this._config.layout)||void 0===i?void 0:i.width)?Math.ceil(1.5*(null===(s=this._config.layout)||void 0===s?void 0:s.width)):500),l=(null===(o=this._config.layout)||void 0===o?void 0:o.width)?2*this._config.layout.width:600,c=document.createElement("style");c.innerHTML=`\n :host {\n --column-max-width: ${d}px;\n --column-width: ${a}px;\n --column-widths: ${null!==(r=null===(n=this._config.layout)||void 0===n?void 0:n.column_widths)&&void 0!==r?r:"none"};\n }\n @media (max-width: ${d}px) {\n .column:first-child > * {\n margin-left: 0;\n }\n .column:last-child > * {\n margin-right: 0;\n }\n }\n @media (max-width: ${l-1}px) {\n .column {\n --column-max-width: ${l}px;\n }\n }\n `,this.shadowRoot.appendChild(c)}connectedCallback(){super.connectedCallback(),this._updateSize()}disconnectedCallback(){super.disconnectedCallback(),this._observer.disconnect()}async _updateSize(){var t,e,i;let s=this.getBoundingClientRect().width,o=0;o=Math.floor(s/((null===(t=this._config.layout)||void 0===t?void 0:t.width)||300)),o=Math.min(o,(null===(e=this._config.layout)||void 0===e?void 0:e.max_cols)||("docked"===(null===(i=this.hass)||void 0===i?void 0:i.dockedSidebar)?3:4)),o=Math.max(o,1),o!==this._columns&&(this._columns=o)}_shouldShow(t,e,i){if(!super._shouldShow(t,e,i))return!1;const s=this._mediaQueries[i];return!s||!!s.matches}isBreak(t){return"layout-break"===t.localName}async _makeLayout(){this._makeColumnLayout()}async _makeColumnLayout(){var t;if(this._observer.disconnect(),!this._columns)return;let e=[];for(let t=0;t{const i=this._config.cards[e];return{card:t,config:i,index:e,show:this._shouldShow(t,i,e)}}));await this._placeColumnCards(e,i.filter((t=>{var e;return(null===(e=this.lovelace)||void 0===e?void 0:e.editMode)||t.show}))),e=e.filter((t=>t.childElementCount>0)),(null===(t=this._config.layout)||void 0===t?void 0:t.rtl)&&e.reverse();const s=this.shadowRoot.querySelector("#columns");for(;s.firstChild;)s.removeChild(s.firstChild);for(const t of e)s.appendChild(t);this.requestUpdate().then((()=>this._observer.observe(this)))}async _placeColumnCards(t,e){}render(){return j`
${this._render_fab()}
`}static get styles(){return[this._fab_styles,it`
@@ -22,22 +22,27 @@ function t(t,e,i,s){var o,n=arguments.length,r=n<3?e:null===s?s=Object.getOwnPro
}
#columns {
- display: flex;
- flex-direction: row;
+ display: grid;
+ grid-auto-columns: minmax(
+ var(--column-width),
+ var(--column-max-width)
+ );
+ grid-template-columns: var(--column-widths);
justify-content: center;
+ justify-items: center;
margin-left: 4px;
margin-right: 4px;
}
.column {
- flex: 1 0 0;
+ grid-row: 1/2;
max-width: var(--column-max-width);
- min-width: 0;
+ width: 100%;
}
.column > * {
display: block;
margin: var(--masonry-view-card-margin, 4px 4px 8px);
}
- `]}}t([J()],$t.prototype,"_columns",void 0),t([J()],$t.prototype,"_config",void 0);customElements.define("masonry-layout",class extends $t{async _placeColumnCards(t,e){var i;const s=(null===(i=this._config.layout)||void 0===i?void 0:i.min_height)||5;function o(){let e=0;for(let i=0;ithis._placeCards()))}else this._mediaQueries.push(null);if(null===(o=this._config.layout)||void 0===o?void 0:o.mediaquery)for(const[t,e]of Object.entries(null===(n=this._config.layout)||void 0===n?void 0:n.mediaquery)){const e=window.matchMedia(t);this._layoutMQs.push(e),e.addEventListener("change",(()=>this._setGridStyles()))}this._setGridStyles()}async updated(t){await super.updated(t),(t.has("cards")||t.has("_editMode"))&&this._placeCards()}async firstUpdated(){this._setGridStyles()}_setGridStyles(){const t=this.shadowRoot.querySelector("#root");if(!t)return;const e=e=>{for(const[i,s]of Object.entries(e))i.startsWith("grid")&&t.style.setProperty(i,s)};t.style.cssText="",this._config.layout&&e(this._config.layout);for(const t of this._layoutMQs)if(t.matches){e(this._config.layout.mediaquery[t.media]);break}}_shouldShow(t,e,i){if(!super._shouldShow(t,e,i))return!1;const s=this._mediaQueries[i];return!s||!!s.matches}_placeCards(){var t,e;const i=this.shadowRoot.querySelector("#root");for(;i.firstChild;)i.removeChild(i.firstChild);let s=this.cards.map(((t,e)=>{const i=this._config.cards[e];return{card:t,config:i,index:e,show:this._shouldShow(t,i,e)}}));for(const o of s.filter((t=>{var e;return(null===(e=this.lovelace)||void 0===e?void 0:e.editMode)||t.show}))){const s=this.getCardElement(o);for(const[i,n]of Object.entries(null!==(e=null===(t=o.config)||void 0===t?void 0:t.view_layout)&&void 0!==e?e:{}))i.startsWith("grid")&&s.style.setProperty(i,n);i.appendChild(s)}}render(){return j`
+ `]}}t([J()],$t.prototype,"_columns",void 0),t([J()],$t.prototype,"_config",void 0);customElements.define("masonry-layout",class extends $t{async _placeColumnCards(t,e){var i;const s=(null===(i=this._config.layout)||void 0===i?void 0:i.min_height)||5;function o(){let e=0;for(let i=0;ithis._placeCards()))}else this._mediaQueries.push(null);if(null===(o=this._config.layout)||void 0===o?void 0:o.mediaquery)for(const[t,e]of Object.entries(null===(n=this._config.layout)||void 0===n?void 0:n.mediaquery)){const e=window.matchMedia(t);this._layoutMQs.push(e),e.addEventListener("change",(()=>this._setGridStyles()))}this._setGridStyles()}async updated(t){await super.updated(t),(t.has("cards")||t.has("_editMode"))&&this._placeCards()}async firstUpdated(){this._setGridStyles()}_setGridStyles(){const t=this.shadowRoot.querySelector("#root");if(!t)return;const e=e=>{for(const[i,s]of Object.entries(e))(i.startsWith("grid")||"grid"===i||"place-items"===i)&&t.style.setProperty(i,s)};t.style.cssText="",this._config.layout&&e(this._config.layout);for(const t of this._layoutMQs)if(t.matches){e(this._config.layout.mediaquery[t.media]);break}}_shouldShow(t,e,i){if(!super._shouldShow(t,e,i))return!1;const s=this._mediaQueries[i];return!s||!!s.matches}_placeCards(){var t,e;const i=this.shadowRoot.querySelector("#root");for(;i.firstChild;)i.removeChild(i.firstChild);let s=this.cards.map(((t,e)=>{const i=this._config.cards[e];return{card:t,config:i,index:e,show:this._shouldShow(t,i,e)}}));for(const o of s.filter((t=>{var e;return(null===(e=this.lovelace)||void 0===e?void 0:e.editMode)||t.show}))){const s=this.getCardElement(o);for(const[i,n]of Object.entries(null!==(e=null===(t=o.config)||void 0===t?void 0:t.view_layout)&&void 0!==e?e:{}))(i.startsWith("grid")||"place-self"===i)&&s.style.setProperty(i,n);i.appendChild(s)}}render(){return j`
${this._render_fab()}`}static get styles(){return[this._fab_styles,it`
:host {
padding-top: 4px;
@@ -202,4 +207,4 @@ function t(t,e,i,s){var o,n=arguments.length,r=n<3?e:null===s?s=Object.getOwnPro
@value-changed=${this._layoutChanged}
>
- `}}),customElements.whenDefined("hui-card-element-editor").then((()=>{const t=customElements.get("hui-card-element-editor"),e=t.prototype.getConfigElement;t.prototype.getConfigElement=async function(){const t=await e.bind(this)();if(t){const e=t.setConfig;t.setConfig=function(t){let i=JSON.parse(JSON.stringify(t));this._layoutData=i.layout,delete i.view_layout,e.bind(this)(i)}}return t};const i=t.prototype._handleUIConfigChanged;t.prototype._handleUIConfigChanged=function(t){this._configElement&&this._configElement._layoutData&&(t.detail.config.view_layout=this._configElement._layoutData),i.bind(this)(t)}}));var Ot="2.2.2";console.info(`%cLAYOUT-CARD ${Ot} IS INSTALLED`,"color: green; font-weight: bold","");
+ `}}),customElements.whenDefined("hui-card-element-editor").then((()=>{const t=customElements.get("hui-card-element-editor"),e=t.prototype.getConfigElement;t.prototype.getConfigElement=async function(){const t=await e.bind(this)();if(t){const e=t.setConfig;t.setConfig=function(t){let i=JSON.parse(JSON.stringify(t));this._layoutData=i.layout,delete i.view_layout,e.bind(this)(i)}}return t};const i=t.prototype._handleUIConfigChanged;t.prototype._handleUIConfigChanged=function(t){this._configElement&&this._configElement._layoutData&&(t.detail.config.view_layout=this._configElement._layoutData),i.bind(this)(t)}}));var Ot="2.2.3";console.info(`%cLAYOUT-CARD ${Ot} IS INSTALLED`,"color: green; font-weight: bold","");
diff --git a/package.json b/package.json
index 0ec0812..242926e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "layout-card",
- "version": "2.2.2",
+ "version": "2.2.3",
"description": "",
"private": true,
"scripts": {
diff --git a/src/layouts/base-column-layout.ts b/src/layouts/base-column-layout.ts
index 5296410..3316c26 100644
--- a/src/layouts/base-column-layout.ts
+++ b/src/layouts/base-column-layout.ts
@@ -56,6 +56,7 @@ export class BaseColumnLayout extends BaseLayout {
async firstUpdated() {
this._updateSize();
+ const column_width = this._config.layout?.width || 300;
const column_max_width =
this._config.layout?.max_width ||
(this._config.layout?.width
@@ -64,10 +65,13 @@ export class BaseColumnLayout extends BaseLayout {
const column_two_width = this._config.layout?.width
? this._config.layout.width * 2
: 600;
+
const styleEl = document.createElement("style");
styleEl.innerHTML = `
:host {
--column-max-width: ${column_max_width}px;
+ --column-width: ${column_width}px;
+ --column-widths: ${this._config.layout?.column_widths ?? "none"};
}
@media (max-width: ${column_max_width}px) {
.column:first-child > * {
@@ -185,16 +189,21 @@ export class BaseColumnLayout extends BaseLayout {
}
#columns {
- display: flex;
- flex-direction: row;
+ display: grid;
+ grid-auto-columns: minmax(
+ var(--column-width),
+ var(--column-max-width)
+ );
+ grid-template-columns: var(--column-widths);
justify-content: center;
+ justify-items: center;
margin-left: 4px;
margin-right: 4px;
}
.column {
- flex: 1 0 0;
+ grid-row: 1/2;
max-width: var(--column-max-width);
- min-width: 0;
+ width: 100%;
}
.column > * {
display: block;
diff --git a/src/layouts/grid.ts b/src/layouts/grid.ts
index 8020166..09c0234 100644
--- a/src/layouts/grid.ts
+++ b/src/layouts/grid.ts
@@ -56,7 +56,7 @@ class GridLayout extends BaseLayout {
if (!root) return;
const addStyles = (layout) => {
for (const [key, value] of Object.entries(layout)) {
- if (key.startsWith("grid"))
+ if (key.startsWith("grid") || key === "grid" || key === "place-items")
root.style.setProperty(key, (value as any) as string);
}
};
@@ -100,7 +100,8 @@ class GridLayout extends BaseLayout {
for (const [key, value] of Object.entries(
card.config?.view_layout ?? {}
)) {
- if (key.startsWith("grid")) el.style.setProperty(key, value as string);
+ if (key.startsWith("grid") || key === "place-self")
+ el.style.setProperty(key, value as string);
}
root.appendChild(el);
}
diff --git a/src/types.ts b/src/types.ts
index d880e71..3ad3d4f 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -36,6 +36,7 @@ export interface ViewConfig {
export interface ColumnViewConfig extends ViewConfig {
layout?: {
width?: number;
+ column_widths: string;
max_width?: number;
max_cols?: number;
min_height?: number;
diff --git a/test/lovelace/grid-view.yaml b/test/lovelace/grid-view.yaml
index 8369110..b1fa2b8 100644
--- a/test/lovelace/grid-view.yaml
+++ b/test/lovelace/grid-view.yaml
@@ -53,9 +53,3 @@ cards:
show_header_toggle: false
view_layout:
grid-area: main
- - type: entities
- entities:
- - light.bed_light
- - type: entities
- entities:
- - light.bed_light