mirror of
https://github.com/home-assistant/frontend.git
synced 2025-10-26 12:09:47 +00:00
Compare commits
20 Commits
card-featu
...
bottom-she
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a16f4952ab | ||
|
|
a4e2070d0f | ||
|
|
fef2ff6392 | ||
|
|
459a0bd178 | ||
|
|
d808c63c55 | ||
|
|
80135af9ec | ||
|
|
848b737a92 | ||
|
|
50d4fae14a | ||
|
|
1da3570a6f | ||
|
|
6c4046c842 | ||
|
|
e97b04b5b9 | ||
|
|
6dbfb60825 | ||
|
|
ba2881b0bf | ||
|
|
e42be13682 | ||
|
|
bf8dacb7a4 | ||
|
|
a23fc8fcba | ||
|
|
0ba88246a1 | ||
|
|
6529b31b65 | ||
|
|
866518477f | ||
|
|
f831f876de |
@@ -1,6 +1,6 @@
|
|||||||
import "@home-assistant/webawesome/dist/components/drawer/drawer";
|
import "@home-assistant/webawesome/dist/components/drawer/drawer";
|
||||||
import { css, html, LitElement, type PropertyValues } from "lit";
|
import { css, html, LitElement, type PropertyValues } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { haStyleScrollbar } from "../resources/styles";
|
import { haStyleScrollbar } from "../resources/styles";
|
||||||
|
|
||||||
export const BOTTOM_SHEET_ANIMATION_DURATION_MS = 300;
|
export const BOTTOM_SHEET_ANIMATION_DURATION_MS = 300;
|
||||||
@@ -14,6 +14,12 @@ export class HaBottomSheet extends LitElement {
|
|||||||
|
|
||||||
@state() private _drawerOpen = false;
|
@state() private _drawerOpen = false;
|
||||||
|
|
||||||
|
@query("#drawer") private _drawer!: HTMLElement;
|
||||||
|
|
||||||
|
private _resizeStartY = 0;
|
||||||
|
|
||||||
|
private _resizeDelta = 0;
|
||||||
|
|
||||||
private _handleAfterHide() {
|
private _handleAfterHide() {
|
||||||
this.open = false;
|
this.open = false;
|
||||||
const ev = new Event("closed", {
|
const ev = new Event("closed", {
|
||||||
@@ -33,19 +39,82 @@ export class HaBottomSheet extends LitElement {
|
|||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
<wa-drawer
|
<wa-drawer
|
||||||
|
id="drawer"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
.open=${this._drawerOpen}
|
.open=${this._drawerOpen}
|
||||||
@wa-after-hide=${this._handleAfterHide}
|
@wa-after-hide=${this._handleAfterHide}
|
||||||
without-header
|
without-header
|
||||||
|
@touchstart=${this._handleTouchStart}
|
||||||
>
|
>
|
||||||
<slot name="header"></slot>
|
<slot name="header"></slot>
|
||||||
<div class="body ha-scrollbar">
|
<div id="body" class="body ha-scrollbar">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</wa-drawer>
|
</wa-drawer>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleTouchStart = (ev: TouchEvent) => {
|
||||||
|
// Check if any element inside drawer in the composed path has scrollTop > 0
|
||||||
|
for (const path of ev.composedPath()) {
|
||||||
|
const el = path as HTMLElement;
|
||||||
|
if (el === this._drawer) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (el.scrollTop > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._startResizing(ev.touches[0].clientY);
|
||||||
|
};
|
||||||
|
|
||||||
|
private _startResizing(clientY: number) {
|
||||||
|
// register event listeners for drag handling
|
||||||
|
document.addEventListener("touchmove", this._handleMouseMove, {
|
||||||
|
passive: false,
|
||||||
|
});
|
||||||
|
document.addEventListener("touchend", this._endResizing);
|
||||||
|
document.addEventListener("touchcancel", this._endResizing);
|
||||||
|
|
||||||
|
this._resizeStartY = clientY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleMouseMove = (ev: TouchEvent) => {
|
||||||
|
this._resizeDelta = this._resizeStartY - ev.touches[0].clientY;
|
||||||
|
if (this._resizeDelta < 0) {
|
||||||
|
ev.preventDefault();
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.style.setProperty(
|
||||||
|
"--dialog-transform",
|
||||||
|
`translateY(${this._resizeDelta * -1}px)`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private _endResizing = () => {
|
||||||
|
this._unregisterResizeHandlers();
|
||||||
|
|
||||||
|
if (this._resizeDelta > -50) {
|
||||||
|
this.style.removeProperty("--dialog-transform");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._drawerOpen = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _unregisterResizeHandlers = () => {
|
||||||
|
document.removeEventListener("touchmove", this._handleMouseMove);
|
||||||
|
document.removeEventListener("touchend", this._unregisterResizeHandlers);
|
||||||
|
document.removeEventListener("touchcancel", this._unregisterResizeHandlers);
|
||||||
|
};
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
this._unregisterResizeHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
static styles = [
|
static styles = [
|
||||||
haStyleScrollbar,
|
haStyleScrollbar,
|
||||||
css`
|
css`
|
||||||
@@ -59,6 +128,7 @@ export class HaBottomSheet extends LitElement {
|
|||||||
wa-drawer::part(dialog) {
|
wa-drawer::part(dialog) {
|
||||||
max-height: var(--ha-bottom-sheet-max-height, 90vh);
|
max-height: var(--ha-bottom-sheet-max-height, 90vh);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
transform: var(--dialog-transform);
|
||||||
}
|
}
|
||||||
wa-drawer::part(body) {
|
wa-drawer::part(body) {
|
||||||
max-width: var(--ha-bottom-sheet-max-width);
|
max-width: var(--ha-bottom-sheet-max-width);
|
||||||
@@ -90,6 +160,11 @@ export class HaBottomSheet extends LitElement {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
padding: var(
|
||||||
|
--ha-bottom-sheet-padding,
|
||||||
|
0 var(--safe-area-inset-right) var(--safe-area-inset-bottom)
|
||||||
|
var(--safe-area-inset-left)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
@@ -99,4 +174,8 @@ declare global {
|
|||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"ha-bottom-sheet": HaBottomSheet;
|
"ha-bottom-sheet": HaBottomSheet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface HASSDomEvents {
|
||||||
|
"bottom-sheet-lock-resize-changed": boolean;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ export class HaFilterDevices extends LitElement {
|
|||||||
}
|
}
|
||||||
search-input-outlined {
|
search-input-outlined {
|
||||||
display: block;
|
display: block;
|
||||||
padding: var(--ha-space-1) var(--ha-space-2) 0;
|
padding: 0 8px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ export class HaFilterDomains extends LitElement {
|
|||||||
}
|
}
|
||||||
search-input-outlined {
|
search-input-outlined {
|
||||||
display: block;
|
display: block;
|
||||||
padding: var(--ha-space-1) var(--ha-space-2) 0;
|
padding: 0 8px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ export class HaFilterEntities extends LitElement {
|
|||||||
}
|
}
|
||||||
search-input-outlined {
|
search-input-outlined {
|
||||||
display: block;
|
display: block;
|
||||||
padding: var(--ha-space-1) var(--ha-space-2) 0;
|
padding: 0 8px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ export class HaFilterIntegrations extends LitElement {
|
|||||||
}
|
}
|
||||||
search-input-outlined {
|
search-input-outlined {
|
||||||
display: block;
|
display: block;
|
||||||
padding: var(--ha-space-1) var(--ha-space-2) 0;
|
padding: 0 8px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -256,7 +256,7 @@ export class HaFilterLabels extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
search-input-outlined {
|
search-input-outlined {
|
||||||
display: block;
|
display: block;
|
||||||
padding: var(--ha-space-1) var(--ha-space-2) 0;
|
padding: 0 8px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import memoizeOne from "memoize-one";
|
|||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { toggleAttribute } from "../common/dom/toggle_attribute";
|
import { toggleAttribute } from "../common/dom/toggle_attribute";
|
||||||
import { stringCompare } from "../common/string/compare";
|
import { stringCompare } from "../common/string/compare";
|
||||||
import { computeRTL } from "../common/util/compute_rtl";
|
|
||||||
import { throttle } from "../common/util/throttle";
|
import { throttle } from "../common/util/throttle";
|
||||||
import { subscribeFrontendUserData } from "../data/frontend";
|
import { subscribeFrontendUserData } from "../data/frontend";
|
||||||
import type { ActionHandlerDetail } from "../data/lovelace/action_handler";
|
import type { ActionHandlerDetail } from "../data/lovelace/action_handler";
|
||||||
@@ -537,17 +536,11 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _renderUserItem(selectedPanel: string) {
|
private _renderUserItem(selectedPanel: string) {
|
||||||
const isRTL = computeRTL(this.hass);
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-md-list-item
|
<ha-md-list-item
|
||||||
href="/profile"
|
href="/profile"
|
||||||
type="link"
|
type="link"
|
||||||
class=${classMap({
|
class="user ${selectedPanel === "profile" ? " selected" : ""}"
|
||||||
user: true,
|
|
||||||
selected: selectedPanel === "profile",
|
|
||||||
rtl: isRTL,
|
|
||||||
})}
|
|
||||||
@mouseenter=${this._itemMouseEnter}
|
@mouseenter=${this._itemMouseEnter}
|
||||||
@mouseleave=${this._itemMouseLeave}
|
@mouseleave=${this._itemMouseLeave}
|
||||||
>
|
>
|
||||||
@@ -673,7 +666,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
tooltip.style.display = "block";
|
tooltip.style.display = "block";
|
||||||
tooltip.style.position = "fixed";
|
tooltip.style.position = "fixed";
|
||||||
tooltip.style.top = `${top}px`;
|
tooltip.style.top = `${top}px`;
|
||||||
tooltip.style.left = `calc(${item.offsetLeft + item.clientWidth + 8}px + var(--safe-area-inset-left, var(--ha-space-0)))`;
|
tooltip.style.left = `calc(${item.offsetLeft + item.clientWidth + 8}px + var(--safe-area-inset-left, 0px))`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _hideTooltip() {
|
private _hideTooltip() {
|
||||||
@@ -712,17 +705,13 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
background-color: var(--sidebar-background-color);
|
background-color: var(--sidebar-background-color);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding-bottom: calc(
|
padding-bottom: calc(14px + var(--safe-area-inset-bottom, 0px));
|
||||||
14px + var(--safe-area-inset-bottom, var(--ha-space-0))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
.menu {
|
.menu {
|
||||||
height: calc(
|
height: calc(var(--header-height) + var(--safe-area-inset-top, 0px));
|
||||||
var(--header-height) + var(--safe-area-inset-top, var(--ha-space-0))
|
|
||||||
);
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 0 var(--ha-space-1);
|
padding: 0 4px;
|
||||||
border-bottom: 1px solid transparent;
|
border-bottom: 1px solid transparent;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-weight: var(--ha-font-weight-normal);
|
font-weight: var(--ha-font-weight-normal);
|
||||||
@@ -737,17 +726,13 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
);
|
);
|
||||||
font-size: var(--ha-font-size-xl);
|
font-size: var(--ha-font-size-xl);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-left: calc(
|
padding-left: calc(4px + var(--safe-area-inset-left, 0px));
|
||||||
var(--ha-space-1) + var(--safe-area-inset-left, var(--ha-space-0))
|
padding-inline-start: calc(4px + var(--safe-area-inset-left, 0px));
|
||||||
);
|
|
||||||
padding-inline-start: calc(
|
|
||||||
var(--ha-space-1) + var(--safe-area-inset-left, var(--ha-space-0))
|
|
||||||
);
|
|
||||||
padding-inline-end: initial;
|
padding-inline-end: initial;
|
||||||
padding-top: var(--safe-area-inset-top, var(--ha-space-0));
|
padding-top: var(--safe-area-inset-top, 0px);
|
||||||
}
|
}
|
||||||
:host([expanded]) .menu {
|
:host([expanded]) .menu {
|
||||||
width: calc(256px + var(--safe-area-inset-left, var(--ha-space-0)));
|
width: calc(256px + var(--safe-area-inset-left, 0px));
|
||||||
}
|
}
|
||||||
:host([narrow][expanded]) .menu {
|
:host([narrow][expanded]) .menu {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -763,8 +748,8 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
:host([narrow]) .title {
|
:host([narrow]) .title {
|
||||||
margin: var(--ha-space-0);
|
margin: 0;
|
||||||
padding: var(--ha-space-0) var(--ha-space-4);
|
padding: 0 16px;
|
||||||
}
|
}
|
||||||
:host([expanded]) .title {
|
:host([expanded]) .title {
|
||||||
display: initial;
|
display: initial;
|
||||||
@@ -776,16 +761,13 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
ha-fade-in,
|
ha-fade-in,
|
||||||
ha-md-list {
|
ha-md-list {
|
||||||
height: calc(
|
height: calc(
|
||||||
100% - var(--header-height) - var(
|
100% - var(--header-height) - var(--safe-area-inset-top, 0px) -
|
||||||
--safe-area-inset-top,
|
|
||||||
var(--ha-space-0)
|
|
||||||
) -
|
|
||||||
132px
|
132px
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-fade-in {
|
ha-fade-in {
|
||||||
padding: var(--ha-space-1) var(--ha-space-0);
|
padding: 4px 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -795,29 +777,29 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
ha-md-list {
|
ha-md-list {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
background: none;
|
background: none;
|
||||||
margin-left: var(--safe-area-inset-left, var(--ha-space-0));
|
margin-left: var(--safe-area-inset-left, 0px);
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-md-list-item {
|
ha-md-list-item {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: var(--ha-space-1);
|
margin: 4px;
|
||||||
border-radius: var(--ha-border-radius-sm);
|
border-radius: var(--ha-border-radius-sm);
|
||||||
--md-list-item-one-line-container-height: var(--ha-space-10);
|
--md-list-item-one-line-container-height: 40px;
|
||||||
--md-list-item-top-space: 0;
|
--md-list-item-top-space: 0;
|
||||||
--md-list-item-bottom-space: 0;
|
--md-list-item-bottom-space: 0;
|
||||||
width: var(--ha-space-12);
|
width: 48px;
|
||||||
position: relative;
|
position: relative;
|
||||||
--md-list-item-label-text-color: var(--sidebar-text-color);
|
--md-list-item-label-text-color: var(--sidebar-text-color);
|
||||||
--md-list-item-leading-space: var(--ha-space-3);
|
--md-list-item-leading-space: 12px;
|
||||||
--md-list-item-trailing-space: var(--ha-space-3);
|
--md-list-item-trailing-space: 12px;
|
||||||
--md-list-item-leading-icon-size: var(--ha-space-6);
|
--md-list-item-leading-icon-size: 24px;
|
||||||
}
|
}
|
||||||
:host([expanded]) ha-md-list-item {
|
:host([expanded]) ha-md-list-item {
|
||||||
width: 248px;
|
width: 248px;
|
||||||
}
|
}
|
||||||
:host([narrow][expanded]) ha-md-list-item {
|
:host([narrow][expanded]) ha-md-list-item {
|
||||||
width: calc(240px - var(--safe-area-inset-left, var(--ha-space-0)));
|
width: calc(240px - var(--safe-area-inset-left, 0px));
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-md-list-item.selected {
|
ha-md-list-item.selected {
|
||||||
@@ -841,7 +823,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
ha-icon[slot="start"],
|
ha-icon[slot="start"],
|
||||||
ha-svg-icon[slot="start"] {
|
ha-svg-icon[slot="start"] {
|
||||||
width: var(--ha-space-6);
|
width: 24px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
color: var(--sidebar-icon-color);
|
color: var(--sidebar-icon-color);
|
||||||
}
|
}
|
||||||
@@ -874,7 +856,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-width: var(--ha-space-2);
|
min-width: 8px;
|
||||||
border-radius: var(--ha-border-radius-xl);
|
border-radius: var(--ha-border-radius-xl);
|
||||||
font-weight: var(--ha-font-weight-normal);
|
font-weight: var(--ha-font-weight-normal);
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
@@ -885,26 +867,22 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
ha-svg-icon + .badge {
|
ha-svg-icon + .badge {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: var(--ha-space-1);
|
top: 4px;
|
||||||
left: 26px;
|
left: 26px;
|
||||||
border-radius: var(--ha-border-radius-md);
|
border-radius: var(--ha-border-radius-md);
|
||||||
font-size: 0.65em;
|
font-size: 0.65em;
|
||||||
line-height: var(--ha-line-height-expanded);
|
line-height: var(--ha-line-height-expanded);
|
||||||
padding: var(--ha-space-0) var(--ha-space-1);
|
padding: 0 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-md-list-item.user {
|
ha-md-list-item.user {
|
||||||
--md-list-item-leading-icon-size: var(--ha-space-10);
|
--md-list-item-leading-icon-size: 40px;
|
||||||
--md-list-item-leading-space: var(--ha-space-1);
|
--md-list-item-leading-space: 4px;
|
||||||
}
|
|
||||||
|
|
||||||
ha-md-list-item.user.rtl {
|
|
||||||
--md-list-item-leading-space: var(--ha-space-3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-user-badge {
|
ha-user-badge {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-right: calc(var(--ha-space-2) * -1);
|
margin-right: -8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.spacer {
|
.spacer {
|
||||||
@@ -916,7 +894,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
color: var(--sidebar-text-color);
|
color: var(--sidebar-text-color);
|
||||||
font-size: var(--ha-font-size-m);
|
font-size: var(--ha-font-size-m);
|
||||||
font-weight: var(--ha-font-weight-medium);
|
font-weight: var(--ha-font-weight-medium);
|
||||||
padding: var(--ha-space-4);
|
padding: 16px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -928,7 +906,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
color: var(--sidebar-background-color);
|
color: var(--sidebar-background-color);
|
||||||
background-color: var(--sidebar-text-color);
|
background-color: var(--sidebar-text-color);
|
||||||
padding: var(--ha-space-1);
|
padding: 4px;
|
||||||
font-weight: var(--ha-font-weight-medium);
|
font-weight: var(--ha-font-weight-medium);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import memoizeOne from "memoize-one";
|
|||||||
import { computeCssColor } from "../../common/color/compute-color";
|
import { computeCssColor } from "../../common/color/compute-color";
|
||||||
import { hex2rgb } from "../../common/color/convert-color";
|
import { hex2rgb } from "../../common/color/convert-color";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { slugify } from "../../common/string/slugify";
|
|
||||||
import {
|
import {
|
||||||
computeDeviceName,
|
computeDeviceName,
|
||||||
computeDeviceNameDisplay,
|
computeDeviceNameDisplay,
|
||||||
@@ -103,7 +102,7 @@ export class HaTargetPickerValueChip extends LitElement {
|
|||||||
${this.type === "entity"
|
${this.type === "entity"
|
||||||
? nothing
|
? nothing
|
||||||
: html`<span role="gridcell">
|
: html`<span role="gridcell">
|
||||||
<ha-tooltip .for="expand-${slugify(this.itemId)}"
|
<ha-tooltip .for="expand-${this.itemId}"
|
||||||
>${this.hass.localize(
|
>${this.hass.localize(
|
||||||
`ui.components.target-picker.expand_${this.type}_id`
|
`ui.components.target-picker.expand_${this.type}_id`
|
||||||
)}
|
)}
|
||||||
@@ -115,13 +114,13 @@ export class HaTargetPickerValueChip extends LitElement {
|
|||||||
)}
|
)}
|
||||||
.path=${mdiUnfoldMoreVertical}
|
.path=${mdiUnfoldMoreVertical}
|
||||||
hide-title
|
hide-title
|
||||||
.id="expand-${slugify(this.itemId)}"
|
.id="expand-${this.itemId}"
|
||||||
.type=${this.type}
|
.type=${this.type}
|
||||||
@click=${this._handleExpand}
|
@click=${this._handleExpand}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
</span>`}
|
</span>`}
|
||||||
<span role="gridcell">
|
<span role="gridcell">
|
||||||
<ha-tooltip .for="remove-${slugify(this.itemId)}">
|
<ha-tooltip .for="remove-${this.itemId}">
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
`ui.components.target-picker.remove_${this.type}_id`
|
`ui.components.target-picker.remove_${this.type}_id`
|
||||||
)}
|
)}
|
||||||
@@ -131,7 +130,7 @@ export class HaTargetPickerValueChip extends LitElement {
|
|||||||
.label=${this.hass.localize("ui.components.target-picker.remove")}
|
.label=${this.hass.localize("ui.components.target-picker.remove")}
|
||||||
.path=${mdiClose}
|
.path=${mdiClose}
|
||||||
hide-title
|
hide-title
|
||||||
.id="remove-${slugify(this.itemId)}"
|
.id="remove-${this.itemId}"
|
||||||
.type=${this.type}
|
.type=${this.type}
|
||||||
@click=${this._removeItem}
|
@click=${this._removeItem}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
|
|||||||
@@ -846,8 +846,7 @@ class DialogAddAutomationElement
|
|||||||
items: true,
|
items: true,
|
||||||
blank:
|
blank:
|
||||||
!this._selectedGroup && !this._filter && this._tab === "groups",
|
!this._selectedGroup && !this._filter && this._tab === "groups",
|
||||||
"empty-search":
|
"empty-search": !items?.length && this._filter,
|
||||||
!items?.length && !filteredBlockItems?.length && this._filter,
|
|
||||||
hidden:
|
hidden:
|
||||||
this._narrow &&
|
this._narrow &&
|
||||||
!this._selectedGroup &&
|
!this._selectedGroup &&
|
||||||
@@ -856,7 +855,7 @@ class DialogAddAutomationElement
|
|||||||
})}
|
})}
|
||||||
@scroll=${this._onItemsScroll}
|
@scroll=${this._onItemsScroll}
|
||||||
>
|
>
|
||||||
${filteredBlockItems
|
${filteredBlockItems && filteredBlockItems.length
|
||||||
? this._renderItemList(
|
? this._renderItemList(
|
||||||
this.hass.localize(`ui.panel.config.automation.editor.blocks`),
|
this.hass.localize(`ui.panel.config.automation.editor.blocks`),
|
||||||
filteredBlockItems
|
filteredBlockItems
|
||||||
@@ -889,7 +888,7 @@ class DialogAddAutomationElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _renderItemList(title, items?: ListItem[]) {
|
private _renderItemList(title, items?: ListItem[]) {
|
||||||
if (!items || !items.length) {
|
if (!items) {
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1118,6 +1117,11 @@ class DialogAddAutomationElement
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.items ha-md-list,
|
||||||
|
.groups {
|
||||||
|
padding-bottom: max(var(--safe-area-inset-bottom), var(--ha-space-3));
|
||||||
|
}
|
||||||
|
|
||||||
.groups {
|
.groups {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
flex: 3;
|
flex: 3;
|
||||||
@@ -1202,11 +1206,6 @@ class DialogAddAutomationElement
|
|||||||
border: 1px solid var(--ha-color-border-neutral-quiet);
|
border: 1px solid var(--ha-color-border-neutral-quiet);
|
||||||
}
|
}
|
||||||
|
|
||||||
.items ha-md-list,
|
|
||||||
.groups {
|
|
||||||
padding-bottom: max(var(--safe-area-inset-bottom), var(--ha-space-3));
|
|
||||||
}
|
|
||||||
|
|
||||||
.items.blank {
|
.items.blank {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,8 +115,8 @@ export class HaConfigLabels extends LitElement {
|
|||||||
borderRadius: "var(--ha-border-radius-md)",
|
borderRadius: "var(--ha-border-radius-md)",
|
||||||
border: "1px solid var(--outline-color)",
|
border: "1px solid var(--outline-color)",
|
||||||
boxSizing: "border-box",
|
boxSizing: "border-box",
|
||||||
width: "var(--ha-space-5)",
|
width: "20px",
|
||||||
height: "var(--ha-space-5)",
|
height: "20px",
|
||||||
})}
|
})}
|
||||||
></div>`
|
></div>`
|
||||||
: nothing,
|
: nothing,
|
||||||
|
|||||||
@@ -1,126 +0,0 @@
|
|||||||
import { html, LitElement, nothing } from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
|
||||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
|
||||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
|
||||||
import "../../../components/ha-control-number-buttons";
|
|
||||||
import { isUnavailableState } from "../../../data/entity";
|
|
||||||
import {
|
|
||||||
MediaPlayerEntityFeature,
|
|
||||||
type MediaPlayerEntity,
|
|
||||||
} from "../../../data/media-player";
|
|
||||||
import type { HomeAssistant } from "../../../types";
|
|
||||||
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
|
|
||||||
import { cardFeatureStyles } from "./common/card-feature-styles";
|
|
||||||
import type {
|
|
||||||
LovelaceCardFeatureContext,
|
|
||||||
MediaPlayerVolumeButtonsCardFeatureConfig,
|
|
||||||
} from "./types";
|
|
||||||
import { clamp } from "../../../common/number/clamp";
|
|
||||||
|
|
||||||
export const supportsMediaPlayerVolumeButtonsCardFeature = (
|
|
||||||
hass: HomeAssistant,
|
|
||||||
context: LovelaceCardFeatureContext
|
|
||||||
) => {
|
|
||||||
const stateObj = context.entity_id
|
|
||||||
? hass.states[context.entity_id]
|
|
||||||
: undefined;
|
|
||||||
if (!stateObj) return false;
|
|
||||||
const domain = computeDomain(stateObj.entity_id);
|
|
||||||
return (
|
|
||||||
domain === "media_player" &&
|
|
||||||
supportsFeature(stateObj, MediaPlayerEntityFeature.VOLUME_SET)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
@customElement("hui-media-player-volume-buttons-card-feature")
|
|
||||||
class HuiMediaPlayerVolumeButtonsCardFeature
|
|
||||||
extends LitElement
|
|
||||||
implements LovelaceCardFeature
|
|
||||||
{
|
|
||||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
|
|
||||||
|
|
||||||
@state() private _config?: MediaPlayerVolumeButtonsCardFeatureConfig;
|
|
||||||
|
|
||||||
private get _stateObj() {
|
|
||||||
if (!this.hass || !this.context || !this.context.entity_id) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return this.hass.states[this.context.entity_id] as
|
|
||||||
| MediaPlayerEntity
|
|
||||||
| undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
static getStubConfig(): MediaPlayerVolumeButtonsCardFeatureConfig {
|
|
||||||
return {
|
|
||||||
type: "media-player-volume-buttons",
|
|
||||||
step: 5,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async getConfigElement(): Promise<LovelaceCardFeatureEditor> {
|
|
||||||
await import(
|
|
||||||
"../editor/config-elements/hui-media-player-volume-buttons-card-feature-editor"
|
|
||||||
);
|
|
||||||
return document.createElement(
|
|
||||||
"hui-media-player-volume-buttons-card-feature-editor"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public setConfig(config: MediaPlayerVolumeButtonsCardFeatureConfig): void {
|
|
||||||
if (!config) {
|
|
||||||
throw new Error("Invalid configuration");
|
|
||||||
}
|
|
||||||
this._config = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render() {
|
|
||||||
if (
|
|
||||||
!this._config ||
|
|
||||||
!this.hass ||
|
|
||||||
!this.context ||
|
|
||||||
!this._stateObj ||
|
|
||||||
!supportsMediaPlayerVolumeButtonsCardFeature(this.hass, this.context)
|
|
||||||
) {
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
const position =
|
|
||||||
this._stateObj.attributes.volume_level != null
|
|
||||||
? Math.round(this._stateObj.attributes.volume_level * 100)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<ha-control-number-buttons
|
|
||||||
.disabled=${!this._stateObj || isUnavailableState(this._stateObj.state)}
|
|
||||||
.locale=${this.hass.locale}
|
|
||||||
min="0"
|
|
||||||
max="100"
|
|
||||||
.step=${this._config.step ?? 5}
|
|
||||||
.value=${position}
|
|
||||||
unit="%"
|
|
||||||
@value-changed=${this._valueChanged}
|
|
||||||
></ha-control-number-buttons>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _valueChanged(ev: CustomEvent) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
|
|
||||||
this.hass!.callService("media_player", "volume_set", {
|
|
||||||
entity_id: this._stateObj!.entity_id,
|
|
||||||
volume_level: clamp(ev.detail.value, 0, 100) / 100,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles() {
|
|
||||||
return cardFeatureStyles;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"hui-media-player-volume-buttons-card-feature": HuiMediaPlayerVolumeButtonsCardFeature;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -50,11 +50,6 @@ export interface MediaPlayerVolumeSliderCardFeatureConfig {
|
|||||||
type: "media-player-volume-slider";
|
type: "media-player-volume-slider";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MediaPlayerVolumeButtonsCardFeatureConfig {
|
|
||||||
type: "media-player-volume-buttons";
|
|
||||||
step?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FanDirectionCardFeatureConfig {
|
export interface FanDirectionCardFeatureConfig {
|
||||||
type: "fan-direction";
|
type: "fan-direction";
|
||||||
}
|
}
|
||||||
@@ -257,7 +252,6 @@ export type LovelaceCardFeatureConfig =
|
|||||||
| LockCommandsCardFeatureConfig
|
| LockCommandsCardFeatureConfig
|
||||||
| LockOpenDoorCardFeatureConfig
|
| LockOpenDoorCardFeatureConfig
|
||||||
| MediaPlayerPlaybackCardFeatureConfig
|
| MediaPlayerPlaybackCardFeatureConfig
|
||||||
| MediaPlayerVolumeButtonsCardFeatureConfig
|
|
||||||
| MediaPlayerVolumeSliderCardFeatureConfig
|
| MediaPlayerVolumeSliderCardFeatureConfig
|
||||||
| NumericInputCardFeatureConfig
|
| NumericInputCardFeatureConfig
|
||||||
| SelectOptionsCardFeatureConfig
|
| SelectOptionsCardFeatureConfig
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import "../card-features/hui-light-color-temp-card-feature";
|
|||||||
import "../card-features/hui-lock-commands-card-feature";
|
import "../card-features/hui-lock-commands-card-feature";
|
||||||
import "../card-features/hui-lock-open-door-card-feature";
|
import "../card-features/hui-lock-open-door-card-feature";
|
||||||
import "../card-features/hui-media-player-playback-card-feature";
|
import "../card-features/hui-media-player-playback-card-feature";
|
||||||
import "../card-features/hui-media-player-volume-buttons-card-feature";
|
|
||||||
import "../card-features/hui-media-player-volume-slider-card-feature";
|
import "../card-features/hui-media-player-volume-slider-card-feature";
|
||||||
import "../card-features/hui-numeric-input-card-feature";
|
import "../card-features/hui-numeric-input-card-feature";
|
||||||
import "../card-features/hui-select-options-card-feature";
|
import "../card-features/hui-select-options-card-feature";
|
||||||
@@ -73,7 +72,6 @@ const TYPES = new Set<LovelaceCardFeatureConfig["type"]>([
|
|||||||
"lock-commands",
|
"lock-commands",
|
||||||
"lock-open-door",
|
"lock-open-door",
|
||||||
"media-player-playback",
|
"media-player-playback",
|
||||||
"media-player-volume-buttons",
|
|
||||||
"media-player-volume-slider",
|
"media-player-volume-slider",
|
||||||
"numeric-input",
|
"numeric-input",
|
||||||
"select-options",
|
"select-options",
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ import { supportsLightColorTempCardFeature } from "../../card-features/hui-light
|
|||||||
import { supportsLockCommandsCardFeature } from "../../card-features/hui-lock-commands-card-feature";
|
import { supportsLockCommandsCardFeature } from "../../card-features/hui-lock-commands-card-feature";
|
||||||
import { supportsLockOpenDoorCardFeature } from "../../card-features/hui-lock-open-door-card-feature";
|
import { supportsLockOpenDoorCardFeature } from "../../card-features/hui-lock-open-door-card-feature";
|
||||||
import { supportsMediaPlayerPlaybackCardFeature } from "../../card-features/hui-media-player-playback-card-feature";
|
import { supportsMediaPlayerPlaybackCardFeature } from "../../card-features/hui-media-player-playback-card-feature";
|
||||||
import { supportsMediaPlayerVolumeButtonsCardFeature } from "../../card-features/hui-media-player-volume-buttons-card-feature";
|
|
||||||
import { supportsMediaPlayerVolumeSliderCardFeature } from "../../card-features/hui-media-player-volume-slider-card-feature";
|
import { supportsMediaPlayerVolumeSliderCardFeature } from "../../card-features/hui-media-player-volume-slider-card-feature";
|
||||||
import { supportsNumericInputCardFeature } from "../../card-features/hui-numeric-input-card-feature";
|
import { supportsNumericInputCardFeature } from "../../card-features/hui-numeric-input-card-feature";
|
||||||
import { supportsSelectOptionsCardFeature } from "../../card-features/hui-select-options-card-feature";
|
import { supportsSelectOptionsCardFeature } from "../../card-features/hui-select-options-card-feature";
|
||||||
@@ -103,7 +102,6 @@ const UI_FEATURE_TYPES = [
|
|||||||
"lock-commands",
|
"lock-commands",
|
||||||
"lock-open-door",
|
"lock-open-door",
|
||||||
"media-player-playback",
|
"media-player-playback",
|
||||||
"media-player-volume-buttons",
|
|
||||||
"media-player-volume-slider",
|
"media-player-volume-slider",
|
||||||
"numeric-input",
|
"numeric-input",
|
||||||
"select-options",
|
"select-options",
|
||||||
@@ -133,7 +131,6 @@ const EDITABLES_FEATURE_TYPES = new Set<UiFeatureTypes>([
|
|||||||
"fan-preset-modes",
|
"fan-preset-modes",
|
||||||
"humidifier-modes",
|
"humidifier-modes",
|
||||||
"lawn-mower-commands",
|
"lawn-mower-commands",
|
||||||
"media-player-volume-buttons",
|
|
||||||
"numeric-input",
|
"numeric-input",
|
||||||
"select-options",
|
"select-options",
|
||||||
"trend-graph",
|
"trend-graph",
|
||||||
@@ -174,7 +171,6 @@ const SUPPORTS_FEATURE_TYPES: Record<
|
|||||||
"lock-commands": supportsLockCommandsCardFeature,
|
"lock-commands": supportsLockCommandsCardFeature,
|
||||||
"lock-open-door": supportsLockOpenDoorCardFeature,
|
"lock-open-door": supportsLockOpenDoorCardFeature,
|
||||||
"media-player-playback": supportsMediaPlayerPlaybackCardFeature,
|
"media-player-playback": supportsMediaPlayerPlaybackCardFeature,
|
||||||
"media-player-volume-buttons": supportsMediaPlayerVolumeButtonsCardFeature,
|
|
||||||
"media-player-volume-slider": supportsMediaPlayerVolumeSliderCardFeature,
|
"media-player-volume-slider": supportsMediaPlayerVolumeSliderCardFeature,
|
||||||
"numeric-input": supportsNumericInputCardFeature,
|
"numeric-input": supportsNumericInputCardFeature,
|
||||||
"select-options": supportsSelectOptionsCardFeature,
|
"select-options": supportsSelectOptionsCardFeature,
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
import { html, LitElement, nothing } from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
|
||||||
import memoizeOne from "memoize-one";
|
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
|
||||||
import "../../../../components/ha-form/ha-form";
|
|
||||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
|
||||||
import type { HomeAssistant } from "../../../../types";
|
|
||||||
import type {
|
|
||||||
LovelaceCardFeatureContext,
|
|
||||||
MediaPlayerVolumeButtonsCardFeatureConfig,
|
|
||||||
} from "../../card-features/types";
|
|
||||||
import type { LovelaceCardFeatureEditor } from "../../types";
|
|
||||||
|
|
||||||
@customElement("hui-media-player-volume-buttons-card-feature-editor")
|
|
||||||
export class HuiMediaPlayerVolumeButtonsCardFeatureEditor
|
|
||||||
extends LitElement
|
|
||||||
implements LovelaceCardFeatureEditor
|
|
||||||
{
|
|
||||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
|
|
||||||
|
|
||||||
@state() private _config?: MediaPlayerVolumeButtonsCardFeatureConfig;
|
|
||||||
|
|
||||||
public setConfig(config: MediaPlayerVolumeButtonsCardFeatureConfig): void {
|
|
||||||
this._config = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _schema = memoizeOne(
|
|
||||||
() =>
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: "step",
|
|
||||||
selector: {
|
|
||||||
number: {
|
|
||||||
mode: "slider",
|
|
||||||
step: 1,
|
|
||||||
min: 1,
|
|
||||||
max: 100,
|
|
||||||
unit_of_measurement: "%",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
] as const
|
|
||||||
);
|
|
||||||
|
|
||||||
protected render() {
|
|
||||||
if (!this.hass || !this._config) {
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data: MediaPlayerVolumeButtonsCardFeatureConfig = {
|
|
||||||
type: "media-player-volume-buttons",
|
|
||||||
step: this._config.step ?? 5,
|
|
||||||
};
|
|
||||||
|
|
||||||
const schema = this._schema();
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<ha-form
|
|
||||||
.hass=${this.hass}
|
|
||||||
.data=${data}
|
|
||||||
.schema=${schema}
|
|
||||||
.computeLabel=${this._computeLabelCallback}
|
|
||||||
@value-changed=${this._valueChanged}
|
|
||||||
></ha-form>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _valueChanged(ev: CustomEvent): void {
|
|
||||||
fireEvent(this, "config-changed", { config: ev.detail.value });
|
|
||||||
}
|
|
||||||
|
|
||||||
private _computeLabelCallback = (
|
|
||||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
|
||||||
) =>
|
|
||||||
this.hass!.localize(
|
|
||||||
`ui.panel.lovelace.editor.features.types.media-player-volume-buttons.${schema.name}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"hui-media-player-volume-buttons-card-feature-editor": HuiMediaPlayerVolumeButtonsCardFeatureEditor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8151,10 +8151,6 @@
|
|||||||
"media-player-playback": {
|
"media-player-playback": {
|
||||||
"label": "Media player playback controls"
|
"label": "Media player playback controls"
|
||||||
},
|
},
|
||||||
"media-player-volume-buttons": {
|
|
||||||
"label": "Media player volume buttons",
|
|
||||||
"step": "Step size"
|
|
||||||
},
|
|
||||||
"media-player-volume-slider": {
|
"media-player-volume-slider": {
|
||||||
"label": "Media player volume slider"
|
"label": "Media player volume slider"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user