import { mdiChevronDown } from "@mdi/js"; import type { PropertyValues, TemplateResult } from "lit"; import { css, html, LitElement, nothing } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { fireEvent } from "../common/dom/fire_event"; import { nextRender } from "../common/util/render-status"; import "./ha-svg-icon"; @customElement("ha-expansion-panel") export class HaExpansionPanel extends LitElement { @property({ type: Boolean, reflect: true }) expanded = false; @property({ type: Boolean, reflect: true }) outlined = false; @property({ attribute: "left-chevron", type: Boolean, reflect: true }) public leftChevron = false; @property({ attribute: "no-collapse", type: Boolean, reflect: true }) public noCollapse = false; @property() header?: string; @property() secondary?: string; @state() _showContent = this.expanded; @query(".container") private _container!: HTMLDivElement; protected render(): TemplateResult { const chevronIcon = this.noCollapse ? nothing : html` `; return html`
${this.leftChevron ? chevronIcon : nothing}
${this.header} ${this.secondary}
${!this.leftChevron ? chevronIcon : nothing}
${this._showContent ? html`` : ""}
`; } protected willUpdate(changedProps: PropertyValues) { super.willUpdate(changedProps); if (changedProps.has("expanded")) { this._showContent = this.expanded; setTimeout(() => { // Verify we're still expanded this._container.style.overflow = this.expanded ? "initial" : "hidden"; }, 300); } } private _handleTransitionEnd() { this._container.style.removeProperty("height"); this._container.style.overflow = this.expanded ? "initial" : "hidden"; this._showContent = this.expanded; } private async _toggleContainer(ev): Promise { if (ev.defaultPrevented) { return; } if (ev.type === "keydown" && ev.key !== "Enter" && ev.key !== " ") { return; } ev.preventDefault(); if (this.noCollapse) { return; } const newExpanded = !this.expanded; fireEvent(this, "expanded-will-change", { expanded: newExpanded }); this._container.style.overflow = "hidden"; if (newExpanded) { this._showContent = true; // allow for dynamic content to be rendered await nextRender(); } const scrollHeight = this._container.scrollHeight; this._container.style.height = `${scrollHeight}px`; if (!newExpanded) { setTimeout(() => { this._container.style.height = "0px"; }, 0); } this.expanded = newExpanded; fireEvent(this, "expanded-changed", { expanded: this.expanded }); } private _focusChanged(ev) { if (this.noCollapse) { return; } this.shadowRoot!.querySelector(".top")!.classList.toggle( "focused", ev.type === "focus" ); } static styles = css` :host { display: block; } .top { display: flex; align-items: center; border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg)); } .top.expanded { border-bottom-left-radius: 0px; border-bottom-right-radius: 0px; } .top.focused { background: var(--input-fill-color); } :host([outlined]) { box-shadow: none; border-width: 1px; border-style: solid; border-color: var(--outline-color); border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg)); } .summary-icon { transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1); direction: var(--direction); margin-left: 8px; margin-inline-start: 8px; margin-inline-end: initial; border-radius: var(--ha-border-radius-circle); } #summary:focus-visible ha-svg-icon.summary-icon { background-color: var(--ha-color-fill-neutral-normal-active); } :host([left-chevron]) .summary-icon, ::slotted([slot="leading-icon"]) { margin-left: 0; margin-right: 8px; margin-inline-start: 0; margin-inline-end: 8px; } #summary { flex: 1; display: flex; padding: var(--expansion-panel-summary-padding, 0 8px); min-height: 48px; align-items: center; cursor: pointer; overflow: hidden; font-weight: var(--ha-font-weight-medium); outline: none; } #summary.noCollapse { cursor: default; } .summary-icon.expanded { transform: rotate(180deg); } .header, ::slotted([slot="header"]) { flex: 1; overflow-wrap: anywhere; } .container { padding: var(--expansion-panel-content-padding, 0 8px); overflow: hidden; transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1); height: 0px; } .container.expanded { height: auto; } .secondary { display: block; color: var(--secondary-text-color); font-size: var(--ha-font-size-s); } `; } declare global { interface HTMLElementTagNameMap { "ha-expansion-panel": HaExpansionPanel; } // for fire event interface HASSDomEvents { "expanded-changed": { expanded: boolean; }; "expanded-will-change": { expanded: boolean; }; } }