mirror of
https://github.com/home-assistant/frontend.git
synced 2026-04-23 11:02:52 +00:00
Compare commits
3 Commits
fix-51548
...
use-contro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e15ffee291 | ||
|
|
1d92b0aebd | ||
|
|
6e991bc32c |
@@ -1,17 +1,22 @@
|
||||
import "@material/mwc-drawer";
|
||||
import "@material/mwc-top-app-bar-fixed";
|
||||
import { mdiMenu } from "@mdi/js";
|
||||
import { mdiMenu, mdiSwapHorizontal } from "@mdi/js";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, query, state } from "lit/decorators";
|
||||
import { dynamicElement } from "../../src/common/dom/dynamic-element-directive";
|
||||
import { setDirectionStyles } from "../../src/common/util/compute_rtl";
|
||||
import "../../src/components/ha-button";
|
||||
import { HaExpansionPanel } from "../../src/components/ha-expansion-panel";
|
||||
import "../../src/components/ha-icon-button";
|
||||
import "../../src/components/ha-svg-icon";
|
||||
import "../../src/managers/notification-manager";
|
||||
import { haStyle } from "../../src/resources/styles";
|
||||
import { PAGES, SIDEBAR } from "../build/import-pages";
|
||||
import "./components/page-description";
|
||||
|
||||
const RTL_STORAGE_KEY = "gallery-rtl";
|
||||
|
||||
const GITHUB_DEMO_URL =
|
||||
"https://github.com/home-assistant/frontend/blob/dev/gallery/src/pages/";
|
||||
|
||||
@@ -29,6 +34,8 @@ class HaGallery extends LitElement {
|
||||
document.location.hash.substring(1) ||
|
||||
`${SIDEBAR[0].category}/${SIDEBAR[0].pages![0]}`;
|
||||
|
||||
@state() private _rtl = localStorage.getItem(RTL_STORAGE_KEY) === "true";
|
||||
|
||||
@query("notification-manager")
|
||||
private _notifications!: HTMLElementTagNameMap["notification-manager"];
|
||||
|
||||
@@ -97,33 +104,43 @@ class HaGallery extends LitElement {
|
||||
${dynamicElement(`demo-${this._page.replace("/", "-")}`)}
|
||||
</div>
|
||||
<div class="page-footer">
|
||||
<div class="header">Help us to improve our documentation</div>
|
||||
<div class="secondary">
|
||||
Suggest an edit to this page, or provide/view feedback for this
|
||||
page.
|
||||
<div class="edit-docs">
|
||||
<div class="header">Help us to improve our documentation</div>
|
||||
<div class="secondary">
|
||||
Suggest an edit to this page, or provide/view feedback for this
|
||||
page.
|
||||
</div>
|
||||
<div>
|
||||
${PAGES[this._page].description ||
|
||||
Object.keys(PAGES[this._page].metadata).length > 0
|
||||
? html`
|
||||
<a
|
||||
href=${`${GITHUB_DEMO_URL}${this._page}.markdown`}
|
||||
target="_blank"
|
||||
>
|
||||
Edit text
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
${PAGES[this._page].demo
|
||||
? html`
|
||||
<a
|
||||
href=${`${GITHUB_DEMO_URL}${this._page}.ts`}
|
||||
target="_blank"
|
||||
>
|
||||
Edit demo
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
${PAGES[this._page].description ||
|
||||
Object.keys(PAGES[this._page].metadata).length > 0
|
||||
? html`
|
||||
<a
|
||||
href=${`${GITHUB_DEMO_URL}${this._page}.markdown`}
|
||||
target="_blank"
|
||||
>
|
||||
Edit text
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
${PAGES[this._page].demo
|
||||
? html`
|
||||
<a
|
||||
href=${`${GITHUB_DEMO_URL}${this._page}.ts`}
|
||||
target="_blank"
|
||||
>
|
||||
Edit demo
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
<div class="rtl-toggle">
|
||||
<ha-icon-button
|
||||
@click=${this._toggleRtl}
|
||||
.label=${this._rtl ? "Switch to LTR" : "Switch to RTL"}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiSwapHorizontal}></ha-svg-icon>
|
||||
</ha-icon-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -138,6 +155,8 @@ class HaGallery extends LitElement {
|
||||
firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
|
||||
this._applyDirection();
|
||||
|
||||
this.addEventListener("show-notification", (ev) =>
|
||||
this._notifications.showDialog({ message: ev.detail.message })
|
||||
);
|
||||
@@ -164,6 +183,11 @@ class HaGallery extends LitElement {
|
||||
|
||||
updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (changedProps.has("_rtl")) {
|
||||
this._applyDirection();
|
||||
}
|
||||
|
||||
if (!changedProps.has("_page")) {
|
||||
return;
|
||||
}
|
||||
@@ -186,6 +210,15 @@ class HaGallery extends LitElement {
|
||||
this._drawer.open = !this._drawer.open;
|
||||
}
|
||||
|
||||
private _toggleRtl() {
|
||||
this._rtl = !this._rtl;
|
||||
localStorage.setItem(RTL_STORAGE_KEY, String(this._rtl));
|
||||
}
|
||||
|
||||
private _applyDirection() {
|
||||
setDirectionStyles(this._rtl ? "rtl" : "ltr", this);
|
||||
}
|
||||
|
||||
static styles = [
|
||||
haStyle,
|
||||
css`
|
||||
@@ -238,11 +271,16 @@ class HaGallery extends LitElement {
|
||||
}
|
||||
|
||||
.page-footer {
|
||||
display: flex;
|
||||
border-radius: var(--ha-border-radius-lg);
|
||||
background-color: var(--primary-background-color);
|
||||
}
|
||||
|
||||
.edit-docs {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
margin: 16px;
|
||||
padding: 16px;
|
||||
border-radius: var(--ha-border-radius-lg);
|
||||
background-color: var(--primary-background-color);
|
||||
}
|
||||
|
||||
.page-footer div {
|
||||
@@ -266,6 +304,18 @@ class HaGallery extends LitElement {
|
||||
margin: 0 8px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.rtl-toggle {
|
||||
padding: var(--ha-space-4);
|
||||
display: inline-flex;
|
||||
align-items: flex-end;
|
||||
margin-top: 12px !important;
|
||||
}
|
||||
|
||||
.rtl-toggle ha-icon-button {
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: var(--ha-border-radius-pill);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { css, html, LitElement } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { applyThemesOnElement } from "../../../../src/common/dom/apply_themes_on_element";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-control-switch";
|
||||
|
||||
@@ -50,59 +51,100 @@ export class DemoHaControlSwitch extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${repeat(switches, (sw) => {
|
||||
const { id, label, ...config } = sw;
|
||||
return html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<label id=${id}>${label}</label>
|
||||
<pre>Config: ${JSON.stringify(config)}</pre>
|
||||
<ha-control-switch
|
||||
.checked=${this.checked}
|
||||
class=${ifDefined(config.class)}
|
||||
@change=${this.handleValueChanged}
|
||||
.pathOn=${mdiLightbulb}
|
||||
.pathOff=${mdiLightbulbOff}
|
||||
.label=${label}
|
||||
?disabled=${config.disabled}
|
||||
?reversed=${config.reversed}
|
||||
>
|
||||
</ha-control-switch>
|
||||
<div class="themes">
|
||||
${["light", "dark"].map(
|
||||
(mode) => html`
|
||||
<div class=${mode}>
|
||||
<ha-card header="ha-control-switch ${mode}">
|
||||
${repeat(switches, (sw) => {
|
||||
const { id, label, ...config } = sw;
|
||||
return html`
|
||||
<div class="card-content">
|
||||
<label id="${mode}-${id}">${label}</label>
|
||||
<pre>Config: ${JSON.stringify(config)}</pre>
|
||||
<ha-control-switch
|
||||
.checked=${this.checked}
|
||||
class=${ifDefined(config.class)}
|
||||
@change=${this.handleValueChanged}
|
||||
.pathOn=${mdiLightbulb}
|
||||
.pathOff=${mdiLightbulbOff}
|
||||
.label=${label}
|
||||
?disabled=${config.disabled}
|
||||
?reversed=${config.reversed}
|
||||
>
|
||||
</ha-control-switch>
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
<div class="card-content">
|
||||
<p class="title"><b>Vertical</b></p>
|
||||
<div class="vertical-switches">
|
||||
${repeat(switches, (sw) => {
|
||||
const { label, ...config } = sw;
|
||||
return html`
|
||||
<ha-control-switch
|
||||
.checked=${this.checked}
|
||||
vertical
|
||||
class=${ifDefined(config.class)}
|
||||
@change=${this.handleValueChanged}
|
||||
.label=${label}
|
||||
.pathOn=${mdiGarageOpen}
|
||||
.pathOff=${mdiGarage}
|
||||
?disabled=${config.disabled}
|
||||
?reversed=${config.reversed}
|
||||
>
|
||||
</ha-control-switch>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
})}
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<p class="title"><b>Vertical</b></p>
|
||||
<div class="vertical-switches">
|
||||
${repeat(switches, (sw) => {
|
||||
const { id, label, ...config } = sw;
|
||||
return html`
|
||||
<ha-control-switch
|
||||
.checked=${this.checked}
|
||||
vertical
|
||||
class=${ifDefined(config.class)}
|
||||
@change=${this.handleValueChanged}
|
||||
.label=${label}
|
||||
.pathOn=${mdiGarageOpen}
|
||||
.pathOff=${mdiGarage}
|
||||
?disabled=${config.disabled}
|
||||
?reversed=${config.reversed}
|
||||
>
|
||||
</ha-control-switch>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
applyThemesOnElement(
|
||||
this.shadowRoot!.querySelector(".dark"),
|
||||
{
|
||||
default_theme: "default",
|
||||
default_dark_theme: "default",
|
||||
themes: {},
|
||||
darkMode: true,
|
||||
theme: "default",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
.themes {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
}
|
||||
.dark,
|
||||
.light {
|
||||
display: block;
|
||||
background-color: var(--primary-background-color);
|
||||
padding: 16px;
|
||||
border-radius: var(--ha-border-radius-md);
|
||||
}
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 24px auto;
|
||||
margin: 0 auto;
|
||||
}
|
||||
pre {
|
||||
margin-top: 0;
|
||||
|
||||
@@ -13,9 +13,9 @@ import {
|
||||
} from "../../data/entity/entity";
|
||||
import { forwardHaptic } from "../../data/haptics";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-control-switch";
|
||||
import "../ha-formfield";
|
||||
import "../ha-icon-button";
|
||||
import "../ha-switch";
|
||||
|
||||
const isOn = (stateObj?: HassEntity) =>
|
||||
stateObj !== undefined &&
|
||||
@@ -35,7 +35,7 @@ export class HaEntityToggle extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.stateObj) {
|
||||
return html` <ha-switch disabled></ha-switch> `;
|
||||
return html`<ha-control-switch disabled></ha-control-switch> `;
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -62,14 +62,14 @@ export class HaEntityToggle extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
const switchTemplate = html`<ha-switch
|
||||
const switchTemplate = html`<ha-control-switch
|
||||
aria-label=${`Toggle ${computeStateName(this.stateObj)} ${
|
||||
this._isOn ? "off" : "on"
|
||||
}`}
|
||||
.checked=${this._isOn}
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
@change=${this._toggleChanged}
|
||||
></ha-switch>`;
|
||||
></ha-control-switch>`;
|
||||
|
||||
if (!this.label) {
|
||||
return switchTemplate;
|
||||
@@ -163,6 +163,10 @@ export class HaEntityToggle extends LitElement {
|
||||
white-space: nowrap;
|
||||
min-width: 38px;
|
||||
}
|
||||
ha-control-switch {
|
||||
--control-switch-thickness: 20px;
|
||||
--control-switch-off-color: var(--state-inactive-color);
|
||||
}
|
||||
ha-icon-button {
|
||||
--ha-icon-button-size: 40px;
|
||||
color: var(--ha-icon-button-inactive-color, var(--primary-text-color));
|
||||
@@ -171,9 +175,6 @@ export class HaEntityToggle extends LitElement {
|
||||
ha-icon-button.state-active {
|
||||
color: var(--ha-icon-button-active-color, var(--primary-color));
|
||||
}
|
||||
ha-switch {
|
||||
padding: 13px 5px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { mainWindow } from "../common/dom/get_main_window";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
@customElement("ha-control-switch")
|
||||
@@ -39,7 +40,7 @@ export class HaControlSwitch extends LitElement {
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(changedProperties);
|
||||
this.setupListeners();
|
||||
this.setupSwipeListeners();
|
||||
}
|
||||
|
||||
private _toggle() {
|
||||
@@ -50,7 +51,19 @@ export class HaControlSwitch extends LitElement {
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.setupListeners();
|
||||
this.setupSwipeListeners();
|
||||
}
|
||||
|
||||
updated(changedProperties: PropertyValues<this>) {
|
||||
super.updated(changedProperties);
|
||||
if (
|
||||
changedProperties.has("disabled") ||
|
||||
changedProperties.has("vertical") ||
|
||||
changedProperties.has("reversed")
|
||||
) {
|
||||
this.destroyListeners();
|
||||
this.setupSwipeListeners();
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
@@ -61,7 +74,11 @@ export class HaControlSwitch extends LitElement {
|
||||
@query("#switch")
|
||||
private switch!: HTMLDivElement;
|
||||
|
||||
setupListeners() {
|
||||
setupSwipeListeners() {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.switch && !this._mc) {
|
||||
this._mc = new Manager(this.switch, {
|
||||
touchAction: this.touchAction ?? (this.vertical ? "pan-x" : "pan-y"),
|
||||
@@ -90,13 +107,15 @@ export class HaControlSwitch extends LitElement {
|
||||
} else {
|
||||
this._mc.on("swiperight", () => {
|
||||
if (this.disabled) return;
|
||||
this.checked = !this.reversed;
|
||||
const isRTL = mainWindow.document.dir === "rtl";
|
||||
this.checked = (!this.reversed && !isRTL) || (this.reversed && isRTL);
|
||||
fireEvent(this, "change");
|
||||
});
|
||||
|
||||
this._mc.on("swipeleft", () => {
|
||||
if (this.disabled) return;
|
||||
this.checked = !!this.reversed;
|
||||
const isRTL = mainWindow.document.dir === "rtl";
|
||||
this.checked = (this.reversed && !isRTL) || (!this.reversed && isRTL);
|
||||
fireEvent(this, "change");
|
||||
});
|
||||
}
|
||||
@@ -116,11 +135,38 @@ export class HaControlSwitch extends LitElement {
|
||||
}
|
||||
|
||||
private _keydown(ev: any) {
|
||||
if (ev.key !== "Enter" && ev.key !== " ") {
|
||||
const supportedKeys = ["Enter", " "];
|
||||
if (this.vertical) {
|
||||
supportedKeys.push("ArrowUp", "ArrowDown");
|
||||
} else {
|
||||
supportedKeys.push("ArrowLeft", "ArrowRight");
|
||||
}
|
||||
if (!supportedKeys.includes(ev.key)) {
|
||||
return;
|
||||
}
|
||||
ev.preventDefault();
|
||||
this._toggle();
|
||||
|
||||
const supportedToggleKeys = ["Enter", " "];
|
||||
|
||||
let allowTurnOn = !this.checked;
|
||||
|
||||
if (this.reversed) {
|
||||
allowTurnOn = !allowTurnOn;
|
||||
}
|
||||
|
||||
if (this.vertical) {
|
||||
supportedToggleKeys.push(allowTurnOn ? "ArrowDown" : "ArrowUp");
|
||||
} else {
|
||||
if (mainWindow.document.dir === "rtl") {
|
||||
allowTurnOn = !allowTurnOn;
|
||||
}
|
||||
|
||||
supportedToggleKeys.push(allowTurnOn ? "ArrowRight" : "ArrowLeft");
|
||||
}
|
||||
|
||||
if (supportedToggleKeys.includes(ev.key)) {
|
||||
this._toggle();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@@ -132,7 +178,7 @@ export class HaControlSwitch extends LitElement {
|
||||
aria-checked=${this.checked ? "true" : "false"}
|
||||
aria-label=${ifDefined(this.label)}
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
tabindex=${ifDefined(this.disabled ? undefined : "0")}
|
||||
?checked=${this.checked}
|
||||
?disabled=${this.disabled}
|
||||
>
|
||||
@@ -156,6 +202,7 @@ export class HaControlSwitch extends LitElement {
|
||||
--control-switch-on-color: var(--primary-color);
|
||||
--control-switch-off-color: var(--disabled-color);
|
||||
--control-switch-background-opacity: 0.2;
|
||||
--control-switch-hover-background-opacity: 0.4;
|
||||
--control-switch-thickness: 40px;
|
||||
--control-switch-border-radius: var(--ha-border-radius-lg);
|
||||
--control-switch-padding: 4px;
|
||||
@@ -167,10 +214,10 @@ export class HaControlSwitch extends LitElement {
|
||||
transition: box-shadow 180ms ease-in-out;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.switch:focus-visible {
|
||||
.switch:not([disabled]):focus-visible {
|
||||
box-shadow: 0 0 0 2px var(--control-switch-off-color);
|
||||
}
|
||||
.switch[checked]:focus-visible {
|
||||
.switch[checked]:not([disabled]):focus-visible {
|
||||
box-shadow: 0 0 0 2px var(--control-switch-on-color);
|
||||
}
|
||||
.switch {
|
||||
@@ -199,6 +246,10 @@ export class HaControlSwitch extends LitElement {
|
||||
transition: background-color 180ms ease-in-out;
|
||||
opacity: var(--control-switch-background-opacity);
|
||||
}
|
||||
.switch:not([disabled]):focus-visible .background,
|
||||
.switch:not([disabled]):hover .background {
|
||||
opacity: var(--control-switch-hover-background-opacity);
|
||||
}
|
||||
.switch .button {
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
@@ -222,12 +273,19 @@ export class HaControlSwitch extends LitElement {
|
||||
transform: translateX(100%);
|
||||
background-color: var(--control-switch-on-color);
|
||||
}
|
||||
.switch[checked] .button:dir(rtl) {
|
||||
transform: translateX(-100%);
|
||||
background-color: var(--control-switch-on-color);
|
||||
}
|
||||
:host([reversed]) .switch {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
:host([reversed]) .switch[checked] .button {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
:host([reversed]) .switch[checked] .button:dir(rtl) {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
:host([vertical]) {
|
||||
width: var(--control-switch-thickness);
|
||||
height: 100%;
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { DOMAINS_TOGGLE } from "../../../common/const";
|
||||
import "../../../components/ha-switch";
|
||||
import "../../../components/ha-control-switch";
|
||||
import type { HaSwitch } from "../../../components/ha-switch";
|
||||
import { forwardHaptic } from "../../../data/haptics";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
@@ -33,7 +33,7 @@ class HuiEntitiesToggle extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-switch
|
||||
<ha-control-switch
|
||||
aria-label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.card.entities.toggle"
|
||||
)}
|
||||
@@ -42,18 +42,19 @@ class HuiEntitiesToggle extends LitElement {
|
||||
return stateObj && stateObj.state === "on";
|
||||
})}
|
||||
@change=${this._callService}
|
||||
></ha-switch>
|
||||
></ha-control-switch>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
width: 38px;
|
||||
display: block;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
ha-switch {
|
||||
padding: 13px 5px;
|
||||
margin: -4px -8px;
|
||||
ha-control-switch {
|
||||
--control-switch-thickness: 20px;
|
||||
--control-switch-off-color: var(--state-inactive-color);
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user