import { mdiDrag, mdiEye, mdiEyeOff } from "@mdi/js"; import type { CSSResultGroup } from "lit"; import { LitElement, css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { repeat } from "lit/directives/repeat"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../common/dom/fire_event"; import { haStyleDialog } from "../../resources/styles"; import type { HomeAssistant } from "../../types"; import "../ha-button"; import { createCloseHeading } from "../ha-dialog"; import "../ha-list"; import "../ha-list-item"; import "../ha-sortable"; import type { DataTableColumnContainer, DataTableColumnData, } from "./ha-data-table"; import type { DataTableSettingsDialogParams } from "./show-dialog-data-table-settings"; @customElement("dialog-data-table-settings") export class DialogDataTableSettings extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @state() private _params?: DataTableSettingsDialogParams; @state() private _columnOrder?: string[]; @state() private _hiddenColumns?: string[]; public showDialog(params: DataTableSettingsDialogParams) { this._params = params; this._columnOrder = params.columnOrder; this._hiddenColumns = params.hiddenColumns; } public closeDialog() { this._params = undefined; fireEvent(this, "dialog-closed", { dialog: this.localName }); } private _sortedColumns = memoizeOne( ( columns: DataTableColumnContainer, columnOrder: string[] | undefined, hiddenColumns: string[] | undefined ) => Object.keys(columns) .filter((col) => !columns[col].hidden) .sort((a, b) => { const orderA = columnOrder?.indexOf(a) ?? -1; const orderB = columnOrder?.indexOf(b) ?? -1; const hiddenA = hiddenColumns?.includes(a) ?? Boolean(columns[a].defaultHidden); const hiddenB = hiddenColumns?.includes(b) ?? Boolean(columns[b].defaultHidden); if (hiddenA !== hiddenB) { return hiddenA ? 1 : -1; } if (orderA !== orderB) { if (orderA === -1) { return 1; } if (orderB === -1) { return -1; } } return orderA - orderB; }) .reduce( (arr, key) => { arr.push({ key, ...columns[key] }); return arr; }, [] as (DataTableColumnData & { key: string })[] ) ); protected render() { if (!this._params) { return nothing; } const localize = this._params.localizeFunc || this.hass.localize; const columns = this._sortedColumns( this._params.columns, this._columnOrder, this._hiddenColumns ); return html` ${repeat( columns, (col) => col.key, (col, _idx) => { const canMove = !col.main && col.moveable !== false; const canHide = !col.main && col.hideable !== false; const isVisible = !(this._columnOrder && this._columnOrder.includes(col.key) ? (this._hiddenColumns?.includes(col.key) ?? col.defaultHidden) : col.defaultHidden); return html`${col.title || col.label || col.key} ${canMove && isVisible ? html`` : nothing} `; } )} ${localize("ui.components.data-table.settings.restore")} ${localize("ui.components.data-table.settings.done")} `; } private _columnMoved(ev: CustomEvent): void { ev.stopPropagation(); if (!this._params) { return; } const { oldIndex, newIndex } = ev.detail; const columns = this._sortedColumns( this._params.columns, this._columnOrder, this._hiddenColumns ); const columnOrder = columns.map((column) => column.key); const option = columnOrder.splice(oldIndex, 1)[0]; columnOrder.splice(newIndex, 0, option); this._columnOrder = columnOrder; this._params!.onUpdate(this._columnOrder, this._hiddenColumns); } private _toggle(ev) { if (!this._params) { return; } const column = ev.target.column; const wasHidden = ev.target.hidden; const hidden = [ ...(this._hiddenColumns ?? Object.entries(this._params.columns) .filter(([_key, col]) => col.defaultHidden) .map(([key]) => key)), ]; if (wasHidden && hidden.includes(column)) { hidden.splice(hidden.indexOf(column), 1); } else if (!wasHidden) { hidden.push(column); } const columns = this._sortedColumns( this._params.columns, this._columnOrder, hidden ); if (!this._columnOrder) { this._columnOrder = columns.map((col) => col.key); } else { const newOrder = this._columnOrder.filter((col) => col !== column); // Array.findLastIndex when supported or core-js polyfill const findLastIndex = ( arr: any[], fn: (item: any, index: number, arr: any[]) => boolean ) => { for (let i = arr.length - 1; i >= 0; i--) { if (fn(arr[i], i, arr)) return i; } return -1; }; let lastMoveable = findLastIndex( newOrder, (col) => col !== column && !hidden.includes(col) && !this._params!.columns[col].main && this._params!.columns[col].moveable !== false ); if (lastMoveable === -1) { lastMoveable = newOrder.length - 1; } columns.forEach((col) => { if (!newOrder.includes(col.key)) { if (col.moveable === false) { newOrder.unshift(col.key); } else { newOrder.splice(lastMoveable + 1, 0, col.key); } if ( col.key !== column && col.defaultHidden && !hidden.includes(col.key) ) { hidden.push(col.key); } } }); this._columnOrder = newOrder; } this._hiddenColumns = hidden; this._params!.onUpdate(this._columnOrder, this._hiddenColumns); } private _reset() { this._columnOrder = undefined; this._hiddenColumns = undefined; this._params!.onUpdate(this._columnOrder, this._hiddenColumns); this.closeDialog(); } static get styles(): CSSResultGroup { return [ haStyleDialog, css` ha-dialog { --mdc-dialog-max-width: 500px; --dialog-z-index: 10; --dialog-content-padding: 0 8px; } @media all and (max-width: 451px) { ha-dialog { --vertical-align-dialog: flex-start; --dialog-surface-margin-top: 250px; --ha-dialog-border-radius: 28px 28px 0 0; --mdc-dialog-min-height: calc(100% - 250px); --mdc-dialog-max-height: calc(100% - 250px); } } ha-list-item { --mdc-list-side-padding: 12px; overflow: visible; } .hidden { color: var(--disabled-text-color); } .handle { cursor: move; /* fallback if grab cursor is unsupported */ cursor: grab; } .actions { display: flex; flex-direction: row; } ha-icon-button { display: block; margin: -12px; } `, ]; } } declare global { interface HTMLElementTagNameMap { "dialog-data-table-settings": DialogDataTableSettings; } }