diff --git a/package.json b/package.json index 649fbea192..d0985aaa78 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,6 @@ "license": "Apache-2.0", "dependencies": { "@material/chips": "^5.0.0", - "@material/data-table": "^5.0.0", - "@material/mwc-base": "^0.13.0", "@material/mwc-button": "^0.13.0", "@material/mwc-checkbox": "^0.13.0", "@material/mwc-dialog": "^0.13.0", diff --git a/src/components/data-table/ha-data-table.ts b/src/components/data-table/ha-data-table.ts index 73a51914b4..a3ea1ff84a 100644 --- a/src/components/data-table/ha-data-table.ts +++ b/src/components/data-table/ha-data-table.ts @@ -1,27 +1,21 @@ -import { repeat } from "lit-html/directives/repeat"; import deepClone from "deep-clone-simple"; -import { - MDCDataTableAdapter, - MDCDataTableFoundation, -} from "@material/data-table"; - import { classMap } from "lit-html/directives/class-map"; +import { scroll } from "lit-virtualizer"; + import { html, query, - queryAll, CSSResult, css, customElement, property, TemplateResult, PropertyValues, + LitElement, } from "lit-element"; -import { BaseElement } from "@material/mwc-base/base-element"; - // eslint-disable-next-line import/no-webpack-loader-syntax // @ts-ignore // tslint:disable-next-line: no-implicit-dependencies @@ -35,6 +29,8 @@ import { HaCheckbox } from "../ha-checkbox"; import { fireEvent } from "../../common/dom/fire_event"; import { nextRender } from "../../common/util/render-status"; import { debounce } from "../../common/util/debounce"; +import { styleMap } from "lit-html/directives/style-map"; +import { ifDefined } from "lit-html/directives/if-defined"; declare global { // for fire event @@ -50,8 +46,7 @@ export interface RowClickedEvent { } export interface SelectionChangedEvent { - id: string; - selected: boolean; + value: string[]; } export interface SortingChangedEvent { @@ -76,6 +71,8 @@ export interface DataTableColumnData extends DataTableSortColumnData { title: string; type?: "numeric" | "icon"; template?: (data: any, row: T) => TemplateResult | string; + width?: string; + grows?: boolean; } export interface DataTableRowData { @@ -84,26 +81,23 @@ export interface DataTableRowData { } @customElement("ha-data-table") -export class HaDataTable extends BaseElement { +export class HaDataTable extends LitElement { @property({ type: Object }) public columns: DataTableColumnContainer = {}; @property({ type: Array }) public data: DataTableRowData[] = []; @property({ type: Boolean }) public selectable = false; + @property({ type: Boolean, attribute: "auto-height" }) + public autoHeight = false; @property({ type: String }) public id = "id"; @property({ type: String }) public filter = ""; - protected mdcFoundation!: MDCDataTableFoundation; - protected readonly mdcFoundationClass = MDCDataTableFoundation; - @query(".mdc-data-table") protected mdcRoot!: HTMLElement; - @queryAll(".mdc-data-table__row") protected rowElements!: HTMLElement[]; @property({ type: Boolean }) private _filterable = false; - @property({ type: Boolean }) private _headerChecked = false; - @property({ type: Boolean }) private _headerIndeterminate = false; - @property({ type: Array }) private _checkedRows: string[] = []; @property({ type: String }) private _filter = ""; @property({ type: String }) private _sortColumn?: string; @property({ type: String }) private _sortDirection: SortingDirection = null; @property({ type: Array }) private _filteredData: DataTableRowData[] = []; @query("slot[name='header']") private _header!: HTMLSlotElement; - @query(".scroller") private _scroller!: HTMLDivElement; + @query(".mdc-data-table__table") private _table!: HTMLDivElement; + private _checkableRowsCount?: number; + private _checkedRows: string[] = []; private _sortColumns: { [key: string]: DataTableSortColumnData; } = {}; @@ -114,18 +108,17 @@ export class HaDataTable extends BaseElement { (value: string) => { this._filter = value; }, - 200, + 100, false ); public clearSelection(): void { - this._headerChecked = false; - this._headerIndeterminate = false; - this.mdcFoundation.handleHeaderRowCheckboxChange(); + this._checkedRows = []; + this._checkedRowsChanged(); } - protected firstUpdated() { - super.firstUpdated(); + protected firstUpdated(properties: PropertyValues) { + super.firstUpdated(properties); this._worker = sortFilterWorker(); } @@ -159,6 +152,12 @@ export class HaDataTable extends BaseElement { this._debounceSearch(this.filter); } + if (properties.has("data")) { + this._checkableRowsCount = this.data.filter( + (row) => row.selectable !== false + ).length; + } + if ( properties.has("data") || properties.has("columns") || @@ -173,7 +172,7 @@ export class HaDataTable extends BaseElement { protected render() { return html`
- + ${this._filterable ? html`
@@ -184,168 +183,151 @@ export class HaDataTable extends BaseElement { ` : ""} -
- - - - ${this.selectable - ? html` - - ` - : ""} - ${Object.entries(this.columns).map((columnEntry) => { - const [key, column] = columnEntry; - const sorted = key === this._sortColumn; - const classes = { - "mdc-data-table__header-cell--numeric": Boolean( - column.type && column.type === "numeric" - ), - "mdc-data-table__header-cell--icon": Boolean( - column.type && column.type === "icon" - ), - sortable: Boolean(column.sortable), - "not-sorted": Boolean(column.sortable && !sorted), - }; - return html` - - `; - })} - - - - ${repeat( - this._filteredData!, - (row: DataTableRowData) => row[this.id], - (row: DataTableRowData) => html` - +
+ ${this.selectable + ? html` +
- ${this.selectable - ? html` -
- ` - : ""} - ${Object.entries(this.columns).map((columnEntry) => { - const [key, column] = columnEntry; - return html` - - `; - })} - + + + ` - )} - -
- - - - ${column.sortable - ? html` - - ` - : ""} - ${column.title} -
- - - - ${column.template - ? column.template(row[key], row) - : row[key]} -
+ : ""} + ${Object.entries(this.columns).map((columnEntry) => { + const [key, column] = columnEntry; + const sorted = key === this._sortColumn; + const classes = { + "mdc-data-table__header-cell--numeric": Boolean( + column.type && column.type === "numeric" + ), + "mdc-data-table__header-cell--icon": Boolean( + column.type && column.type === "icon" + ), + sortable: Boolean(column.sortable), + "not-sorted": Boolean(column.sortable && !sorted), + grows: Boolean(column.grows), + }; + return html` +
+ ${column.sortable + ? html` + + ` + : ""} + ${column.title} +
+ `; + })} +
+
+ ${scroll({ + items: this._filteredData, + renderItem: (row: DataTableRowData) => html` +
+ ${this.selectable + ? html` +
+ + +
+ ` + : ""} + ${Object.entries(this.columns).map((columnEntry) => { + const [key, column] = columnEntry; + return html` +
+ ${column.template + ? column.template(row[key], row) + : row[key]} +
+ `; + })} +
+ `, + })} +
`; } - protected createAdapter(): MDCDataTableAdapter { - return { - addClassAtRowIndex: (rowIndex: number, cssClasses: string) => { - if (!(this.rowElements[rowIndex] as any).selectable) { - return; - } - this.rowElements[rowIndex].classList.add(cssClasses); - }, - getRowCount: () => this.rowElements.length, - getRowElements: () => this.rowElements, - getRowIdAtIndex: (rowIndex: number) => this._getRowIdAtIndex(rowIndex), - getRowIndexByChildElement: (el: Element) => - Array.prototype.indexOf.call(this.rowElements, el.closest("tr")), - getSelectedRowCount: () => this._checkedRows.length, - isCheckboxAtRowIndexChecked: (rowIndex: number) => - this._checkedRows.includes(this._getRowIdAtIndex(rowIndex)), - isHeaderRowCheckboxChecked: () => this._headerChecked, - isRowsSelectable: () => this.selectable, - notifyRowSelectionChanged: () => undefined, - notifySelectedAll: () => undefined, - notifyUnselectedAll: () => undefined, - registerHeaderRowCheckbox: () => undefined, - registerRowCheckboxes: () => undefined, - removeClassAtRowIndex: (rowIndex: number, cssClasses: string) => { - this.rowElements[rowIndex].classList.remove(cssClasses); - }, - setAttributeAtRowIndex: ( - rowIndex: number, - attr: string, - value: string - ) => { - this.rowElements[rowIndex].setAttribute(attr, value); - }, - setHeaderRowCheckboxChecked: (checked: boolean) => { - this._headerChecked = checked; - }, - setHeaderRowCheckboxIndeterminate: (indeterminate: boolean) => { - this._headerIndeterminate = indeterminate; - }, - setRowCheckboxCheckedAtIndex: (rowIndex: number, checked: boolean) => { - if (!(this.rowElements[rowIndex] as any).selectable) { - return; - } - this._setRowChecked(this._getRowIdAtIndex(rowIndex), checked); - }, - }; - } - private async _filterData() { const startTime = new Date().getTime(); this.curRequest++; @@ -373,14 +355,10 @@ export class HaDataTable extends BaseElement { this._filteredData = data; } - private _getRowIdAtIndex(rowIndex: number): string { - return this.rowElements[rowIndex].getAttribute("data-row-id")!; - } - private _handleHeaderClick(ev: Event) { - const columnId = (ev.target as HTMLElement) - .closest("th")! - .getAttribute("data-column-id")!; + const columnId = ((ev.target as HTMLElement).closest( + ".mdc-data-table__header-cell" + ) as any).columnId; if (!this.columns[columnId].sortable) { return; } @@ -400,19 +378,32 @@ export class HaDataTable extends BaseElement { }); } - private _handleHeaderRowCheckboxChange(ev: Event) { + private _handleHeaderRowCheckboxClick(ev: Event) { const checkbox = ev.target as HaCheckbox; - this._headerChecked = checkbox.checked; - this._headerIndeterminate = checkbox.indeterminate; - this.mdcFoundation.handleHeaderRowCheckboxChange(); + if (checkbox.checked) { + this._checkedRows = this._filteredData + .filter((data) => data.selectable !== false) + .map((data) => data[this.id]); + this._checkedRowsChanged(); + } else { + this._checkedRows = []; + this._checkedRowsChanged(); + } } - private _handleRowCheckboxChange(ev: Event) { + private _handleRowCheckboxClick(ev: Event) { const checkbox = ev.target as HaCheckbox; - const rowId = checkbox.closest("tr")!.getAttribute("data-row-id"); + const rowId = (checkbox.closest(".mdc-data-table__row") as any).rowId; - this._setRowChecked(rowId!, checkbox.checked); - this.mdcFoundation.handleRowCheckboxChange(ev); + if (checkbox.checked) { + if (this._checkedRows.includes(rowId)) { + return; + } + this._checkedRows = [...this._checkedRows, rowId]; + } else { + this._checkedRows = this._checkedRows.filter((row) => row !== rowId); + } + this._checkedRowsChanged(); } private _handleRowClick(ev: Event) { @@ -420,26 +411,15 @@ export class HaDataTable extends BaseElement { if (target.tagName === "HA-CHECKBOX") { return; } - const rowId = target.closest("tr")!.getAttribute("data-row-id")!; + const rowId = (target.closest(".mdc-data-table__row") as any).rowId; fireEvent(this, "row-click", { id: rowId }, { bubbles: false }); } - private _setRowChecked(rowId: string, checked: boolean) { - if (checked) { - if (this._checkedRows.includes(rowId)) { - return; - } - this._checkedRows = [...this._checkedRows, rowId]; - } else { - const index = this._checkedRows.indexOf(rowId); - if (index === -1) { - return; - } - this._checkedRows.splice(index, 1); - } + private _checkedRowsChanged() { + // force scroller to update, change it's items + this._filteredData = [...this._filteredData]; fireEvent(this, "selection-changed", { - id: rowId, - selected: checked, + value: this._checkedRows, }); } @@ -447,15 +427,20 @@ export class HaDataTable extends BaseElement { this._debounceSearch(ev.detail.value); } - private async _calcScrollHeight() { + private async _calcTableHeight() { + if (this.autoHeight) { + return; + } await this.updateComplete; - this._scroller.style.height = `calc(100% - ${this._header.clientHeight}px)`; + this._table.style.height = `calc(100% - ${this._header.clientHeight}px)`; } static get styles(): CSSResult { return css` /* default mdc styles, colors changed, without checkbox styles */ - + :host { + height: 100%; + } .mdc-data-table__content { font-family: Roboto, sans-serif; -moz-osx-font-smoothing: grayscale; @@ -477,7 +462,7 @@ export class HaDataTable extends BaseElement { display: inline-flex; flex-direction: column; box-sizing: border-box; - overflow-x: auto; + overflow: hidden; } .mdc-data-table__row--selected { @@ -485,12 +470,13 @@ export class HaDataTable extends BaseElement { } .mdc-data-table__row { - border-top-color: rgba(var(--rgb-primary-text-color), 0.12); + display: flex; + width: 100%; + height: 52px; } - .mdc-data-table__row { - border-top-width: 1px; - border-top-style: solid; + .mdc-data-table__row ~ .mdc-data-table__row { + border-top: 1px solid rgba(var(--rgb-primary-text-color), 0.12); } .mdc-data-table__row:not(.mdc-data-table__row--selected):hover { @@ -507,16 +493,24 @@ export class HaDataTable extends BaseElement { .mdc-data-table__header-row { height: 56px; + display: flex; + width: 100%; + border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12); + overflow-x: auto; } - .mdc-data-table__row { - height: 52px; + .mdc-data-table__header-row::-webkit-scrollbar { + display: none; } .mdc-data-table__cell, .mdc-data-table__header-cell { padding-right: 16px; padding-left: 16px; + align-self: center; + overflow: hidden; + text-overflow: ellipsis; + flex-shrink: 0; } .mdc-data-table__header-cell--checkbox, @@ -538,10 +532,10 @@ export class HaDataTable extends BaseElement { } .mdc-data-table__table { + height: 100%; width: 100%; border: 0; white-space: nowrap; - border-collapse: collapse; } .mdc-data-table__cell { @@ -568,12 +562,20 @@ export class HaDataTable extends BaseElement { .mdc-data-table__cell--icon { color: var(--secondary-text-color); text-align: center; + } + + .mdc-data-table__header-cell--icon, + .mdc-data-table__cell--icon { width: 24px; } - .mdc-data-table__header-cell--icon { + .mdc-data-table__header-cell.mdc-data-table__header-cell--icon { text-align: center; } + .mdc-data-table__header-cell.sortable.mdc-data-table__header-cell--icon:hover, + .mdc-data-table__header-cell.sortable.mdc-data-table__header-cell--icon:not(.not-sorted) { + text-align: left; + } .mdc-data-table__cell--icon:first-child ha-icon { margin-left: 8px; @@ -604,6 +606,10 @@ export class HaDataTable extends BaseElement { .mdc-data-table__header-cell--numeric { text-align: right; } + .mdc-data-table__header-cell--numeric.sortable:hover, + .mdc-data-table__header-cell--numeric.sortable:not(.not-sorted) { + text-align: left; + } [dir="rtl"] .mdc-data-table__header-cell--numeric, .mdc-data-table__header-cell--numeric[dir="rtl"] { /* @noflip */ @@ -634,27 +640,21 @@ export class HaDataTable extends BaseElement { cursor: pointer; } .mdc-data-table__header-cell > * { - transition: left 0.2s ease 0s; + transition: left 0.2s ease; } .mdc-data-table__header-cell ha-icon { - top: 15px; + top: -3px; position: absolute; } .mdc-data-table__header-cell.not-sorted ha-icon { left: -20px; } - .mdc-data-table__header-cell:not(.not-sorted) span, - .mdc-data-table__header-cell.not-sorted:hover span { + .mdc-data-table__header-cell.sortable:not(.not-sorted) span, + .mdc-data-table__header-cell.sortable.not-sorted:hover span { left: 24px; } - .mdc-data-table__header-cell.mdc-data-table__header-cell--numeric:not(.not-sorted) - span, - .mdc-data-table__header-cell.mdc-data-table__header-cell--numeric.not-sorted:hover - span { - left: 12px; - } - .mdc-data-table__header-cell:not(.not-sorted) ha-icon, - .mdc-data-table__header-cell:hover.not-sorted ha-icon { + .mdc-data-table__header-cell.sortable:not(.not-sorted) ha-icon, + .mdc-data-table__header-cell.sortable:hover.not-sorted ha-icon { left: 12px; } .table-header { @@ -664,14 +664,24 @@ export class HaDataTable extends BaseElement { position: relative; top: 2px; } - .scroller { - overflow: auto; - } slot[name="header"] { display: block; } - .secondary { - color: var(--secondary-text-color); + .center { + text-align: center; + } + .scroller { + display: flex; + position: relative; + contain: strict; + height: calc(100% - 57px); + } + .mdc-data-table__table:not(.auto-height) .scroller { + overflow: auto; + } + .grows { + flex-grow: 1; + flex-shrink: 1; } `; } diff --git a/src/panels/config/devices/ha-config-devices-dashboard.ts b/src/panels/config/devices/ha-config-devices-dashboard.ts index e41431e52b..a0c05669d4 100644 --- a/src/panels/config/devices/ha-config-devices-dashboard.ts +++ b/src/panels/config/devices/ha-config-devices-dashboard.ts @@ -26,6 +26,7 @@ import { RowClickedEvent, } from "../../../components/data-table/ha-data-table"; import { navigate } from "../../../common/navigate"; +import { HASSDomEvent } from "../../../common/dom/fire_event"; @customElement("ha-config-devices-dashboard") export class HaConfigDeviceDashboard extends LitElement { @@ -127,6 +128,7 @@ export class HaConfigDeviceDashboard extends LitElement { sortable: true, filterable: true, direction: "asc", + grows: true, template: (name, device: DataTableRowData) => { const battery = device.battery_entity ? this.hass.states[device.battery_entity] @@ -155,6 +157,7 @@ export class HaConfigDeviceDashboard extends LitElement { ), sortable: true, filterable: true, + grows: true, direction: "asc", }, manufacturer: { @@ -163,6 +166,7 @@ export class HaConfigDeviceDashboard extends LitElement { ), sortable: true, filterable: true, + width: "15%", }, model: { title: this.hass.localize( @@ -170,6 +174,7 @@ export class HaConfigDeviceDashboard extends LitElement { ), sortable: true, filterable: true, + width: "15%", }, area: { title: this.hass.localize( @@ -177,6 +182,7 @@ export class HaConfigDeviceDashboard extends LitElement { ), sortable: true, filterable: true, + width: "15%", }, integration: { title: this.hass.localize( @@ -184,6 +190,7 @@ export class HaConfigDeviceDashboard extends LitElement { ), sortable: true, filterable: true, + width: "15%", }, battery_entity: { title: this.hass.localize( @@ -191,6 +198,7 @@ export class HaConfigDeviceDashboard extends LitElement { ), sortable: true, type: "numeric", + width: "60px", template: (batteryEntity: string) => { const battery = batteryEntity ? this.hass.states[batteryEntity] @@ -247,8 +255,8 @@ export class HaConfigDeviceDashboard extends LitElement { return batteryEntity ? batteryEntity.entity_id : undefined; } - private _handleRowClicked(ev: CustomEvent) { - const deviceId = (ev.detail as RowClickedEvent).id; + private _handleRowClicked(ev: HASSDomEvent) { + const deviceId = ev.detail.id; navigate(this, `/config/devices/device/${deviceId}`); } } diff --git a/src/panels/config/devices/ha-devices-data-table.ts b/src/panels/config/devices/ha-devices-data-table.ts index 8a05bd1f95..faed6f27dd 100644 --- a/src/panels/config/devices/ha-devices-data-table.ts +++ b/src/panels/config/devices/ha-devices-data-table.ts @@ -134,6 +134,7 @@ export class HaDevicesDataTable extends LitElement { sortable: true, filterable: true, direction: "asc", + grows: true, template: (name, device: DataTableRowData) => { const battery = device.battery_entity ? this.hass.states[device.battery_entity] @@ -163,6 +164,7 @@ export class HaDevicesDataTable extends LitElement { sortable: true, filterable: true, direction: "asc", + grows: true, }, manufacturer: { title: this.hass.localize( @@ -170,6 +172,7 @@ export class HaDevicesDataTable extends LitElement { ), sortable: true, filterable: true, + width: "15%", }, model: { title: this.hass.localize( @@ -177,6 +180,7 @@ export class HaDevicesDataTable extends LitElement { ), sortable: true, filterable: true, + width: "15%", }, area: { title: this.hass.localize( @@ -184,6 +188,7 @@ export class HaDevicesDataTable extends LitElement { ), sortable: true, filterable: true, + width: "15%", }, integration: { title: this.hass.localize( @@ -191,6 +196,7 @@ export class HaDevicesDataTable extends LitElement { ), sortable: true, filterable: true, + width: "15%", }, battery_entity: { title: this.hass.localize( @@ -198,6 +204,7 @@ export class HaDevicesDataTable extends LitElement { ), sortable: true, type: "numeric", + width: "60px", template: (batteryEntity: string) => { const battery = batteryEntity ? this.hass.states[batteryEntity] diff --git a/src/panels/config/entities/ha-config-entities.ts b/src/panels/config/entities/ha-config-entities.ts index 4e61cdde59..af9761fef6 100644 --- a/src/panels/config/entities/ha-config-entities.ts +++ b/src/panels/config/entities/ha-config-entities.ts @@ -49,6 +49,7 @@ import { classMap } from "lit-html/directives/class-map"; import { computeStateName } from "../../../common/entity/compute_state_name"; // tslint:disable-next-line: no-duplicate-imports import { HaTabsSubpageDataTable } from "../../../layouts/hass-tabs-subpage-data-table"; +import { HASSDomEvent } from "../../../common/dom/fire_event"; export interface StateEntity extends EntityRegistryEntry { readonly?: boolean; @@ -96,6 +97,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { sortable: true, filterable: true, direction: "asc", + grows: true, }, }; @@ -106,6 +108,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { type: "icon", sortable: true, filterable: true, + width: "55px", template: (_status, entity: any) => entity.unavailable || entity.disabled_by || entity.readonly ? html` @@ -166,6 +169,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { ), sortable: true, filterable: true, + width: "20%", }; columns.platform = { title: this.hass.localize( @@ -173,6 +177,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { ), sortable: true, filterable: true, + width: "20%", template: (platform) => this.hass.localize(`component.${platform}.config.title`) || platform, }; @@ -467,16 +472,10 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { this._filter = ev.detail.value; } - private _handleSelectionChanged(ev: CustomEvent): void { - const changedSelection = ev.detail as SelectionChangedEvent; - const entity = changedSelection.id; - if (changedSelection.selected) { - this._selectedEntities = [...this._selectedEntities, entity]; - } else { - this._selectedEntities = this._selectedEntities.filter( - (entityId) => entityId !== entity - ); - } + private _handleSelectionChanged( + ev: HASSDomEvent + ): void { + this._selectedEntities = ev.detail.value; } private _enableSelected() { diff --git a/src/panels/config/helpers/ha-config-helpers.ts b/src/panels/config/helpers/ha-config-helpers.ts index 520bb75371..7dafdb725e 100644 --- a/src/panels/config/helpers/ha-config-helpers.ts +++ b/src/panels/config/helpers/ha-config-helpers.ts @@ -39,8 +39,8 @@ export class HaConfigHelpers extends LitElement { @property() private _stateItems: HassEntity[] = []; private _columns = memoize( - (_language): DataTableColumnContainer => { - return { + (narrow, _language): DataTableColumnContainer => { + const columns: DataTableColumnContainer = { icon: { title: "", type: "icon", @@ -54,28 +54,45 @@ export class HaConfigHelpers extends LitElement { ), sortable: true, filterable: true, + grows: true, direction: "asc", template: (name, item: any) => html` ${name} -
- ${item.entity_id} -
- `, - }, - type: { - title: this.hass.localize( - "ui.panel.config.helpers.picker.headers.type" - ), - sortable: true, - filterable: true, - template: (type) => - html` - ${this.hass.localize(`ui.panel.config.helpers.types.${type}`) || - type} + ${narrow + ? html` +
+ ${item.entity_id} +
+ ` + : ""} `, }, }; + if (!narrow) { + columns.entity_id = { + title: this.hass.localize( + "ui.panel.config.helpers.picker.headers.entity_id" + ), + sortable: true, + filterable: true, + width: "30%", + }; + } + columns.type = { + title: this.hass.localize( + "ui.panel.config.helpers.picker.headers.type" + ), + sortable: true, + width: "30%", + filterable: true, + template: (type) => + html` + ${this.hass.localize(`ui.panel.config.helpers.types.${type}`) || + type} + `, + }; + return columns; } ); @@ -106,7 +123,7 @@ export class HaConfigHelpers extends LitElement { back-path="/config" .route=${this.route} .tabs=${configSections.automation} - .columns=${this._columns(this.hass.language)} + .columns=${this._columns(this.narrow, this.hass.language)} .data=${this._getItems(this._stateItems)} @row-click=${this._openEditDialog} > diff --git a/src/panels/config/integrations/config-entry/ha-config-entry-page.ts b/src/panels/config/integrations/config-entry/ha-config-entry-page.ts index bc2923aed4..514b796d53 100755 --- a/src/panels/config/integrations/config-entry/ha-config-entry-page.ts +++ b/src/panels/config/integrations/config-entry/ha-config-entry-page.ts @@ -194,12 +194,14 @@ class HaConfigEntryPage extends LitElement { return css` .content { padding: 4px; + height: 100%; } p { text-align: center; } ha-devices-data-table { width: 100%; + height: 100%; } `; } diff --git a/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts b/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts index 2404323893..f65e118b5e 100644 --- a/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts +++ b/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts @@ -61,6 +61,7 @@ export class HaConfigLovelaceDashboards extends LitElement { sortable: true, filterable: true, direction: "asc", + grows: true, template: (title, dashboard: any) => { const titleTemplate = html` ${title} @@ -101,6 +102,7 @@ export class HaConfigLovelaceDashboards extends LitElement { ), sortable: true, filterable: true, + width: "15%", template: (mode) => html` ${this.hass.localize( @@ -113,6 +115,7 @@ export class HaConfigLovelaceDashboards extends LitElement { title: this.hass.localize( "ui.panel.config.lovelace.dashboards.picker.headers.filename" ), + width: "15%", sortable: true, filterable: true, }; @@ -123,6 +126,7 @@ export class HaConfigLovelaceDashboards extends LitElement { ), sortable: true, type: "icon", + width: "100px", template: (requireAdmin: boolean) => requireAdmin ? html` @@ -137,6 +141,7 @@ export class HaConfigLovelaceDashboards extends LitElement { "ui.panel.config.lovelace.dashboards.picker.headers.sidebar" ), type: "icon", + width: "100px", template: (sidebar) => sidebar ? html` @@ -151,6 +156,7 @@ export class HaConfigLovelaceDashboards extends LitElement { columns.url_path = { title: "", filterable: true, + width: "75px", template: (urlPath) => narrow ? html` diff --git a/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts b/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts index bda6b20e9b..bda2497f13 100644 --- a/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts +++ b/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts @@ -57,6 +57,7 @@ export class HaConfigLovelaceRescources extends LitElement { sortable: true, filterable: true, direction: "asc", + grows: true, }, type: { title: this.hass.localize( @@ -64,6 +65,7 @@ export class HaConfigLovelaceRescources extends LitElement { ), sortable: true, filterable: true, + width: "30%", template: (type) => html` ${this.hass.localize( diff --git a/src/panels/config/zha/zha-add-group-page.ts b/src/panels/config/zha/zha-add-group-page.ts index 6ce4e71f3e..733c040297 100644 --- a/src/panels/config/zha/zha-add-group-page.ts +++ b/src/panels/config/zha/zha-add-group-page.ts @@ -25,6 +25,7 @@ import { PolymerChangedEvent } from "../../../polymer-types"; import "@polymer/paper-spinner/paper-spinner"; import "@material/mwc-button"; import { PaperInputElement } from "@polymer/paper-input/paper-input"; +import { HASSDomEvent } from "../../../common/dom/fire_event"; @customElement("zha-add-group-page") export class ZHAAddGroupPage extends LitElement { @@ -82,7 +83,6 @@ export class ZHAAddGroupPage extends LitElement { .narrow=${this.narrow} selectable @selection-changed=${this._handleAddSelectionChanged} - class="table" > @@ -114,21 +114,10 @@ export class ZHAAddGroupPage extends LitElement { this.devices = await fetchGroupableDevices(this.hass!); } - private _handleAddSelectionChanged(ev: CustomEvent): void { - const changedSelection = ev.detail as SelectionChangedEvent; - const entity = changedSelection.id; - if ( - changedSelection.selected && - !this._selectedDevicesToAdd.includes(entity) - ) { - this._selectedDevicesToAdd.push(entity); - } else { - const index = this._selectedDevicesToAdd.indexOf(entity); - if (index !== -1) { - this._selectedDevicesToAdd.splice(index, 1); - } - } - this._selectedDevicesToAdd = [...this._selectedDevicesToAdd]; + private _handleAddSelectionChanged( + ev: HASSDomEvent + ): void { + this._selectedDevicesToAdd = ev.detail.value; } private async _createGroup(): Promise { @@ -168,11 +157,6 @@ export class ZHAAddGroupPage extends LitElement { float: right; } - .table { - height: 400px; - overflow: auto; - } - ha-config-section *:last-child { padding-bottom: 24px; } diff --git a/src/panels/config/zha/zha-clusters-data-table.ts b/src/panels/config/zha/zha-clusters-data-table.ts index db28edad38..817fc71310 100644 --- a/src/panels/config/zha/zha-clusters-data-table.ts +++ b/src/panels/config/zha/zha-clusters-data-table.ts @@ -49,6 +49,7 @@ export class ZHAClustersDataTable extends LitElement { title: "Name", sortable: true, direction: "asc", + grows: true, }, } : { @@ -56,6 +57,7 @@ export class ZHAClustersDataTable extends LitElement { title: "Name", sortable: true, direction: "asc", + grows: true, }, id: { title: "ID", @@ -65,10 +67,12 @@ export class ZHAClustersDataTable extends LitElement { `; }, sortable: true, + width: "15%", }, endpoint_id: { title: "Endpoint ID", sortable: true, + width: "15%", }, } ); @@ -80,6 +84,7 @@ export class ZHAClustersDataTable extends LitElement { .data=${this._clusters(this.clusters)} .id=${"cluster_id"} selectable + auto-height > `; } diff --git a/src/panels/config/zha/zha-config-dashboard.ts b/src/panels/config/zha/zha-config-dashboard.ts index 4e9e273ed7..71ab7d646e 100644 --- a/src/panels/config/zha/zha-config-dashboard.ts +++ b/src/panels/config/zha/zha-config-dashboard.ts @@ -63,6 +63,7 @@ class ZHAConfigDashboard extends LitElement { sortable: true, filterable: true, direction: "asc", + grows: true, }, } : { @@ -71,16 +72,19 @@ class ZHAConfigDashboard extends LitElement { sortable: true, filterable: true, direction: "asc", + grows: true, }, nwk: { title: "Nwk", sortable: true, filterable: true, + width: "15%", }, ieee: { title: "IEEE", sortable: true, filterable: true, + width: "25%", }, } ); @@ -139,6 +143,7 @@ class ZHAConfigDashboard extends LitElement { .data=${this._memoizeDevices(this._devices)} @row-click=${this._handleDeviceClicked} .id=${"ieee"} + auto-height > diff --git a/src/panels/config/zha/zha-devices-data-table.ts b/src/panels/config/zha/zha-devices-data-table.ts index c647f3c430..619d81bb90 100644 --- a/src/panels/config/zha/zha-devices-data-table.ts +++ b/src/panels/config/zha/zha-devices-data-table.ts @@ -53,6 +53,7 @@ export class ZHADevicesDataTable extends LitElement { sortable: true, filterable: true, direction: "asc", + grows: true, template: (name) => html`
${name} @@ -66,6 +67,7 @@ export class ZHADevicesDataTable extends LitElement { sortable: true, filterable: true, direction: "asc", + grows: true, template: (name) => html`
${name} @@ -76,11 +78,13 @@ export class ZHADevicesDataTable extends LitElement { title: "Manufacturer", sortable: true, filterable: true, + width: "20%", }, model: { title: "Model", sortable: true, filterable: true, + width: "20%", }, } ); @@ -91,6 +95,7 @@ export class ZHADevicesDataTable extends LitElement { .columns=${this._columns(this.narrow)} .data=${this._devices(this.devices)} .selectable=${this.selectable} + auto-height > `; } diff --git a/src/panels/config/zha/zha-group-binding.ts b/src/panels/config/zha/zha-group-binding.ts index 29023d8177..87fee75b82 100644 --- a/src/panels/config/zha/zha-group-binding.ts +++ b/src/panels/config/zha/zha-group-binding.ts @@ -32,6 +32,7 @@ import { HomeAssistant } from "../../../types"; import { ItemSelectedEvent } from "./types"; import "@polymer/paper-item/paper-item"; import { SelectionChangedEvent } from "../../../components/data-table/ha-data-table"; +import { HASSDomEvent } from "../../../common/dom/fire_event"; @customElement("zha-group-binding-control") export class ZHAGroupBindingControl extends LitElement { @@ -200,21 +201,11 @@ export class ZHAGroupBindingControl extends LitElement { } } - private _handleClusterSelectionChanged(event: CustomEvent): void { - const changedSelection = event.detail as SelectionChangedEvent; - const clusterId = changedSelection.id; - if ( - changedSelection.selected && - !this._selectedClusters.includes(clusterId) - ) { - this._selectedClusters.push(clusterId); - } else { - const index = this._selectedClusters.indexOf(clusterId); - if (index !== -1) { - this._selectedClusters.splice(index, 1); - } - } - this._selectedClusters = [...this._selectedClusters]; + private _handleClusterSelectionChanged( + ev: HASSDomEvent + ): void { + this._selectedClusters = ev.detail.value; + this._clustersToBind = []; for (const clusterIndex of this._selectedClusters) { const selectedCluster = this._clusters.find((cluster) => { diff --git a/src/panels/config/zha/zha-group-page.ts b/src/panels/config/zha/zha-group-page.ts index afd265c2a4..cb2dce1c0b 100644 --- a/src/panels/config/zha/zha-group-page.ts +++ b/src/panels/config/zha/zha-group-page.ts @@ -31,6 +31,7 @@ import "@polymer/paper-icon-button/paper-icon-button"; import "@polymer/paper-spinner/paper-spinner"; import "@material/mwc-button"; import { SelectionChangedEvent } from "../../../components/data-table/ha-data-table"; +import { HASSDomEvent } from "../../../common/dom/fire_event"; @customElement("zha-group-page") export class ZHAGroupPage extends LitElement { @@ -145,7 +146,6 @@ export class ZHAGroupPage extends LitElement { .narrow=${this.narrow} selectable @selection-changed=${this._handleRemoveSelectionChanged} - class="table" > @@ -180,7 +180,6 @@ export class ZHAGroupPage extends LitElement { .narrow=${this.narrow} selectable @selection-changed=${this._handleAddSelectionChanged} - class="table" > @@ -223,38 +222,16 @@ export class ZHAGroupPage extends LitElement { }); } - private _handleAddSelectionChanged(ev: CustomEvent): void { - const changedSelection = ev.detail as SelectionChangedEvent; - const entity = changedSelection.id; - if ( - changedSelection.selected && - !this._selectedDevicesToAdd.includes(entity) - ) { - this._selectedDevicesToAdd.push(entity); - } else { - const index = this._selectedDevicesToAdd.indexOf(entity); - if (index !== -1) { - this._selectedDevicesToAdd.splice(index, 1); - } - } - this._selectedDevicesToAdd = [...this._selectedDevicesToAdd]; + private _handleAddSelectionChanged( + ev: HASSDomEvent + ): void { + this._selectedDevicesToAdd = ev.detail.value; } - private _handleRemoveSelectionChanged(ev: CustomEvent): void { - const changedSelection = ev.detail as SelectionChangedEvent; - const entity = changedSelection.id; - if ( - changedSelection.selected && - !this._selectedDevicesToRemove.includes(entity) - ) { - this._selectedDevicesToRemove.push(entity); - } else { - const index = this._selectedDevicesToRemove.indexOf(entity); - if (index !== -1) { - this._selectedDevicesToRemove.splice(index, 1); - } - } - this._selectedDevicesToRemove = [...this._selectedDevicesToRemove]; + private _handleRemoveSelectionChanged( + ev: HASSDomEvent + ): void { + this._selectedDevicesToRemove = ev.detail.value; } private async _addMembersToGroup(): Promise { @@ -309,11 +286,6 @@ export class ZHAGroupPage extends LitElement { float: right; } - .table { - height: 200px; - overflow: auto; - } - mwc-button paper-spinner { width: 14px; height: 14px; diff --git a/src/panels/config/zha/zha-groups-dashboard.ts b/src/panels/config/zha/zha-groups-dashboard.ts index 4e21dfa5a9..cee06458ca 100644 --- a/src/panels/config/zha/zha-groups-dashboard.ts +++ b/src/panels/config/zha/zha-groups-dashboard.ts @@ -19,6 +19,7 @@ import "@polymer/paper-spinner/paper-spinner"; import "@polymer/paper-icon-button/paper-icon-button"; import { navigate } from "../../../common/navigate"; import "../../../layouts/hass-subpage"; +import { HASSDomEvent } from "../../../common/dom/fire_event"; @customElement("zha-groups-dashboard") export class ZHAGroupsDashboard extends LitElement { @@ -102,21 +103,12 @@ export class ZHAGroupsDashboard extends LitElement { this._groups = (await fetchGroups(this.hass!)).sort(sortZHAGroups); } - private _handleRemoveSelectionChanged(ev: CustomEvent): void { - const changedSelection = ev.detail as SelectionChangedEvent; - const groupId = Number(changedSelection.id); - if ( - changedSelection.selected && - !this._selectedGroupsToRemove.includes(groupId) - ) { - this._selectedGroupsToRemove.push(groupId); - } else { - const index = this._selectedGroupsToRemove.indexOf(groupId); - if (index !== -1) { - this._selectedGroupsToRemove.splice(index, 1); - } - } - this._selectedGroupsToRemove = [...this._selectedGroupsToRemove]; + private _handleRemoveSelectionChanged( + ev: HASSDomEvent + ): void { + this._selectedGroupsToRemove = ev.detail.value.map((value) => + Number(value) + ); } private async _removeGroup(): Promise { diff --git a/src/panels/config/zha/zha-groups-data-table.ts b/src/panels/config/zha/zha-groups-data-table.ts index 6a543cbab4..a47cca603c 100644 --- a/src/panels/config/zha/zha-groups-data-table.ts +++ b/src/panels/config/zha/zha-groups-data-table.ts @@ -52,6 +52,7 @@ export class ZHAGroupsDataTable extends LitElement { sortable: true, filterable: true, direction: "asc", + grows: true, template: (name) => html`
${name} @@ -65,6 +66,7 @@ export class ZHAGroupsDataTable extends LitElement { sortable: true, filterable: true, direction: "asc", + grows: true, template: (name) => html`
${name} @@ -73,6 +75,8 @@ export class ZHAGroupsDataTable extends LitElement { }, group_id: { title: this.hass.localize("ui.panel.config.zha.groups.group_id"), + type: "numeric", + width: "15%", template: (groupId: number) => { return html` ${formatAsPaddedHex(groupId)} @@ -82,6 +86,8 @@ export class ZHAGroupsDataTable extends LitElement { }, members: { title: this.hass.localize("ui.panel.config.zha.groups.members"), + type: "numeric", + width: "15%", template: (members: ZHADevice[]) => { return html` ${members.length} @@ -98,6 +104,7 @@ export class ZHAGroupsDataTable extends LitElement { .columns=${this._columns(this.narrow)} .data=${this._groups(this.groups)} .selectable=${this.selectable} + auto-height > `; } diff --git a/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts b/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts index 486af15178..2535949f8d 100644 --- a/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts +++ b/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts @@ -34,7 +34,7 @@ import { computeUnusedEntities } from "../../common/compute-unused-entities"; import { HomeAssistant } from "../../../../types"; import { Lovelace } from "../../types"; import { LovelaceConfig } from "../../../../data/lovelace"; -import { fireEvent } from "../../../../common/dom/fire_event"; +import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event"; import { addEntitiesToLovelaceView } from "../add-entities-to-view"; @customElement("hui-unused-entities") @@ -55,19 +55,33 @@ export class HuiUnusedEntities extends LitElement { private _columns = memoizeOne((narrow: boolean) => { const columns: DataTableColumnContainer = { - entity: { + icon: { + title: "", + type: "icon", + template: (_icon, entity: any) => html` + + `, + }, + name: { title: this.hass!.localize("ui.panel.lovelace.unused_entities.entity"), sortable: true, filterable: true, - filterKey: "friendly_name", + grows: true, direction: "asc", - template: (stateObj) => html` + template: (name, entity: any) => html`
- - ${stateObj.friendly_name} + ${name} + ${narrow + ? html` +
+ ${entity.stateObj.entity_id} +
+ ` + : ""}
`, }, @@ -81,11 +95,13 @@ export class HuiUnusedEntities extends LitElement { title: this.hass!.localize("ui.panel.lovelace.unused_entities.entity_id"), sortable: true, filterable: true, + width: "30%", }; columns.domain = { title: this.hass!.localize("ui.panel.lovelace.unused_entities.domain"), sortable: true, filterable: true, + width: "15%", }; columns.last_changed = { title: this.hass!.localize( @@ -93,6 +109,7 @@ export class HuiUnusedEntities extends LitElement { ), type: "numeric", sortable: true, + width: "15%", template: (lastChanged: string) => html` -
- ${this.hass.localize( - "ui.panel.lovelace.unused_entities.available_entities" - )} - ${this.lovelace.mode === "storage" - ? html` -
${this.hass.localize( - "ui.panel.lovelace.unused_entities.select_to_add" + ${!this.narrow + ? html` + +
+ ${this.hass.localize( + "ui.panel.lovelace.unused_entities.available_entities" )} - ` - : ""} -
-
+ ${this.lovelace.mode === "storage" + ? html` +
${this.hass.localize( + "ui.panel.lovelace.unused_entities.select_to_add" + )} + ` + : ""} +
+ + ` + : ""} { const stateObj = this.hass!.states[entity]; return { + icon: "", entity_id: entity, - entity: { - ...stateObj, - friendly_name: computeStateName(stateObj), - }, + stateObj, + name: computeStateName(stateObj), domain: computeDomain(entity), last_changed: stateObj!.last_changed, }; @@ -178,23 +198,16 @@ export class HuiUnusedEntities extends LitElement { this._unusedEntities = computeUnusedEntities(this.hass, this._config!); } - private _handleSelectionChanged(ev: CustomEvent): void { - const changedSelection = ev.detail as SelectionChangedEvent; - const entity = changedSelection.id; - if (changedSelection.selected) { - this._selectedEntities.push(entity); - } else { - const index = this._selectedEntities.indexOf(entity); - if (index !== -1) { - this._selectedEntities.splice(index, 1); - } - } + private _handleSelectionChanged( + ev: HASSDomEvent + ): void { + this._selectedEntities = ev.detail.value; } private _handleEntityClicked(ev: Event) { - const entityId = (ev.target as HTMLElement) - .closest("tr")! - .getAttribute("data-row-id")!; + const entityId = ((ev.target as HTMLElement).closest( + ".mdc-data-table__row" + ) as any).rowId; fireEvent(this, "hass-more-info", { entityId, }); @@ -214,20 +227,27 @@ export class HuiUnusedEntities extends LitElement { return css` :host { background: var(--lovelace-background); - padding: 16px; - box-sizing: border-box; + display: flex; + flex-direction: column; + } + ha-card { + --ha-card-box-shadow: none; + --ha-card-border-radius: 0; + } + ha-data-table { + --data-table-border-width: 0; + flex-grow: 1; + margin-top: -20px; } ha-fab { - position: sticky; - float: right; + position: absolute; + right: 16px; bottom: 16px; z-index: 1; } ha-fab.rtl { - float: left; - } - ha-card { - margin-bottom: 16px; + left: 16px; + right: auto; } `; } diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts index 3e300badc3..890d88d2fd 100644 --- a/src/panels/lovelace/hui-root.ts +++ b/src/panels/lovelace/hui-root.ts @@ -6,6 +6,7 @@ import { CSSResult, css, property, + query, } from "lit-element"; import { classMap } from "lit-html/directives/class-map"; import "@polymer/app-layout/app-header-layout/app-header-layout"; @@ -60,6 +61,7 @@ class HUIRoot extends LitElement { @property() public route?: { path: string; prefix: string }; @property() private _routeData?: { view: string }; @property() private _curView?: number | "hass-unused-entities"; + @query("ha-app-layout") private _appLayout!: HTMLElement; private _viewCache?: { [viewId: string]: HUIView }; private _debouncedConfigChanged: () => void; @@ -344,7 +346,8 @@ class HUIRoot extends LitElement { }
`; @@ -468,7 +471,7 @@ class HUIRoot extends LitElement { if (changedProperties.has("route")) { const views = this.config.views; - if (this.route!.path === "" && views) { + if (this.route!.path === "" && views.length) { navigate(this, `${this.route!.prefix}/${views[0].path || 0}`, true); newSelectView = 0; } else if (this._routeData!.view === "hass-unused-entities") { @@ -495,8 +498,6 @@ class HUIRoot extends LitElement { if (!oldLovelace || oldLovelace.config !== this.lovelace!.config) { // On config change, recreate the current view from scratch. force = true; - // Recalculate to see if we need to adjust content area for tab bar - fireEvent(this, "iron-resize"); } if (!oldLovelace || oldLovelace.editMode !== this.lovelace!.editMode) { @@ -506,13 +507,11 @@ class HUIRoot extends LitElement { this._routeData!.view === "hass-unused-entities" ) { const views = this.config && this.config.views; - navigate(this, `${this.route?.prefix}/${views[0].path || 0}`); + navigate(this, `${this.route?.prefix}/${views[0]?.path || 0}`); newSelectView = 0; } // On edit mode change, recreate the current view from scratch force = true; - // Recalculate to see if we need to adjust content area for tab bar - fireEvent(this, "iron-resize"); } } @@ -578,14 +577,14 @@ class HUIRoot extends LitElement { } this.lovelace!.setEditMode(true); if (this.config.views.length < 2) { - fireEvent(this, "iron-resize"); + this.updateComplete.then(() => fireEvent(this._appLayout, "iron-resize")); } } private _editModeDisable(): void { this.lovelace!.setEditMode(false); if (this.config.views.length < 2) { - fireEvent(this, "iron-resize"); + this.updateComplete.then(() => fireEvent(this._appLayout, "iron-resize")); } } diff --git a/src/translations/en.json b/src/translations/en.json index f615545ed8..4370783005 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -811,6 +811,7 @@ "picker": { "headers": { "name": "Name", + "entity_id": "Entity ID", "type": "Type", "editable": "Editable" }, diff --git a/yarn.lock b/yarn.lock index a1626633ad..2333fddba3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1515,24 +1515,6 @@ "@material/typography" "^5.0.0" tslib "^1.9.3" -"@material/data-table@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@material/data-table/-/data-table-5.0.0.tgz#441f4bde8f4206273cbc304baf26d003cac4ea8d" - integrity sha512-h7GHVGwStqeBigkWrr+5/VWX6iqCG1eXWoD/my7YfBZzOOcJ3xvSfF+jNPchK0bRPSzX06jhaNRvGOmu3HCAzg== - dependencies: - "@material/animation" "^5.0.0" - "@material/base" "^5.0.0" - "@material/checkbox" "^5.0.0" - "@material/density" "^5.0.0" - "@material/dom" "^5.0.0" - "@material/elevation" "^5.0.0" - "@material/feature-targeting" "^5.0.0" - "@material/rtl" "^5.0.0" - "@material/shape" "^5.0.0" - "@material/theme" "^5.0.0" - "@material/typography" "^5.0.0" - tslib "^1.10.0" - "@material/density@^5.0.0": version "5.0.0" resolved "https://registry.yarnpkg.com/@material/density/-/density-5.0.0.tgz#643d9bd1a5d89b3985d48fd1d6572f73d05fb2e9"