Merge branch 'color-palettes' of github.com:home-assistant/frontend into ha-button

This commit is contained in:
Wendelin 2025-07-28 11:27:23 +02:00
commit 6eb8261525
No known key found for this signature in database
31 changed files with 1541 additions and 711 deletions

View File

@ -26,7 +26,7 @@
"license": "Apache-2.0",
"type": "module",
"dependencies": {
"@babel/runtime": "7.27.6",
"@babel/runtime": "7.28.2",
"@braintree/sanitize-url": "7.1.1",
"@codemirror/autocomplete": "6.18.6",
"@codemirror/commands": "6.8.1",
@ -101,6 +101,7 @@
"comlink": "4.4.2",
"core-js": "3.44.0",
"cropperjs": "1.6.2",
"culori": "4.0.2",
"date-fns": "4.1.0",
"date-fns-tz": "3.2.0",
"deep-clone-simple": "1.1.1",
@ -153,17 +154,18 @@
"@babel/plugin-transform-runtime": "7.28.0",
"@babel/preset-env": "7.28.0",
"@bundle-stats/plugin-webpack-filter": "4.21.0",
"@lokalise/node-api": "14.9.1",
"@lokalise/node-api": "15.0.0",
"@octokit/auth-oauth-device": "8.0.1",
"@octokit/plugin-retry": "8.0.1",
"@octokit/rest": "22.0.0",
"@rsdoctor/rspack-plugin": "1.1.8",
"@rspack/cli": "1.4.8",
"@rspack/core": "1.4.8",
"@rsdoctor/rspack-plugin": "1.1.10",
"@rspack/cli": "1.4.10",
"@rspack/core": "1.4.10",
"@types/babel__plugin-transform-runtime": "7.9.5",
"@types/chromecast-caf-receiver": "6.0.22",
"@types/chromecast-caf-sender": "1.0.11",
"@types/color-name": "2.0.0",
"@types/culori": "4",
"@types/html-minifier-terser": "7.0.2",
"@types/js-yaml": "4.0.9",
"@types/leaflet": "1.9.20",
@ -188,7 +190,7 @@
"eslint-import-resolver-webpack": "0.13.10",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-lit": "2.1.1",
"eslint-plugin-lit-a11y": "5.1.0",
"eslint-plugin-lit-a11y": "5.1.1",
"eslint-plugin-unused-imports": "4.1.4",
"eslint-plugin-wc": "3.0.1",
"fancy-log": "2.0.0",
@ -216,7 +218,7 @@
"terser-webpack-plugin": "5.3.14",
"ts-lit-plugin": "2.0.2",
"typescript": "5.8.3",
"typescript-eslint": "8.37.0",
"typescript-eslint": "8.38.0",
"vite-tsconfig-paths": "5.1.4",
"vitest": "3.2.4",
"webpack-stats-plugin": "1.1.3",
@ -232,7 +234,8 @@
"@fullcalendar/daygrid": "6.1.18",
"globals": "16.3.0",
"tslib": "2.8.1",
"@material/mwc-list@^0.27.0": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch"
"@material/mwc-list@^0.27.0": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch",
"@vaadin/vaadin-themable-mixin": "24.7.9"
},
"packageManager": "yarn@4.9.2"
}

115
src/common/color/palette.ts Normal file
View File

@ -0,0 +1,115 @@
import { formatHex, oklch, wcagLuminance, type Oklch } from "culori";
const MIN_LUMINANCE = 0.3;
const MAX_LUMINANCE = 0.6;
/**
* Normalizes the luminance of a given color to ensure it falls within the specified minimum and maximum luminance range.
* This helps to keep everything readable and accessible, especially for text and UI elements.
*
* This function converts the input color to the OKLCH color space, calculates its luminance,
* and adjusts the lightness component if the luminance is outside the allowed range.
* The adjustment is performed using a binary search to find the appropriate lightness value.
* If the color is already within the range, it is returned unchanged.
*
* @param color - HEX color string
* @returns The normalized color as a hex string, or the original color if normalization is not needed.
* @throws If the provided color is invalid or cannot be parsed.
*/
export const normalizeLuminance = (color: string): string => {
const baseOklch = oklch(color);
if (baseOklch === undefined) {
throw new Error("Invalid color provided");
}
const luminance = wcagLuminance(baseOklch);
if (luminance >= MIN_LUMINANCE && luminance <= MAX_LUMINANCE) {
return color;
}
const targetLuminance =
luminance < MIN_LUMINANCE ? MIN_LUMINANCE : MAX_LUMINANCE;
function findLightness(lowL = 0, highL = 1, iterations = 10) {
if (iterations <= 0) {
return (lowL + highL) / 2;
}
const midL = (lowL + highL) / 2;
const testColor = { ...baseOklch, l: midL } as Oklch;
const testLuminance = wcagLuminance(testColor);
if (Math.abs(testLuminance - targetLuminance) < 0.01) {
return midL;
}
if (testLuminance < targetLuminance) {
return findLightness(midL, highL, iterations--);
}
return findLightness(lowL, midL, iterations--);
}
baseOklch.l = findLightness();
return formatHex(baseOklch) || color;
};
/**
* Generates a color palette based on a base color using the OKLCH color space.
*
* The palette consists of multiple shades, both lighter and darker than the base color,
* calculated by adjusting the lightness and chroma values. Each shade is labeled and
* returned as a tuple containing the shade name and its hexadecimal color value.
*
* @param baseColor - The base color in a HEX format.
* @param label - A string label used to name each color variant in the palette.
* @param steps - An array of numbers representing the percentage steps for generating shades (default: [5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95]).
* @returns An array of tuples, each containing the shade name and its corresponding hex color value.
* @throws If the provided base color is invalid or cannot be parsed by the `oklch` function.
*/
export const generateColorPalette = (
baseColor: string,
label: string,
steps = [5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95]
) => {
const baseOklch = oklch(baseColor);
if (baseOklch === undefined) {
throw new Error("Invalid base color provided");
}
return steps.map((step) => {
const name = `color-${label}-${step}`;
// Base color at 50%
if (step === 50) {
return [name, formatHex(baseOklch)];
}
// For darker shades (below 50%)
if (step < 50) {
const darkFactor = step / 50;
// Adjust lightness and chroma to create darker variants
const darker = {
...baseOklch,
l: baseOklch.l * darkFactor, // darkening
c: baseOklch.c * (0.9 + 0.1 * darkFactor), // Slightly adjust chroma
};
return [name, formatHex(darker)];
}
// For lighter shades (above 50%)
const lightFactor = (step - 50) / 45; // Normalized from 0 to 1
// Adjust lightness and reduce chroma for lighter variants
const lighter = {
...baseOklch,
l: Math.min(1, baseOklch.l + (1 - baseOklch.l) * lightFactor), // Increase lightness
c: baseOklch.c * Math.max(0, 1 - lightFactor * 0.7), // Gradually reduce chroma
};
return [name, formatHex(lighter)];
});
};

View File

@ -1,5 +1,5 @@
import type { ThemeVars } from "../../data/ws-themes";
import { darkColorVariables } from "../../resources/theme/color.globals";
import { darkColorVariables } from "../../resources/theme/color";
import { derivedStyles } from "../../resources/theme/theme";
import type { HomeAssistant } from "../../types";
import {
@ -11,6 +11,7 @@ import {
} from "../color/convert-color";
import { hexBlend } from "../color/hex";
import { labBrighten, labDarken } from "../color/lab";
import { generateColorPalette } from "../color/palette";
import { rgbContrast } from "../color/rgb";
interface ProcessedTheme {
@ -75,6 +76,11 @@ export const applyThemesOnElement = (
const labPrimaryColor = rgb2lab(rgbPrimaryColor);
themeRules["primary-color"] = primaryColor;
const rgbLightPrimaryColor = lab2rgb(labBrighten(labPrimaryColor));
generateColorPalette(primaryColor, "primary").forEach(([key, color]) => {
themeRules[key] = color;
});
themeRules["light-primary-color"] = rgb2hex(rgbLightPrimaryColor);
themeRules["dark-primary-color"] = lab2hex(labDarken(labPrimaryColor));
themeRules["darker-primary-color"] = lab2hex(

View File

@ -18,7 +18,43 @@ const _extractCssVars = (
return variables;
};
export const extractVar = (css: CSSResult, varName: string) => {
/**
* Recursively resolves a CSS variable reference from a base variable map.
*
* If the value of the specified variable in `baseVars` is itself a CSS variable reference
* (i.e., starts with `var(`), this function will recursively resolve the reference until
* it finds a concrete value or reaches an undefined variable.
*
* @param varName - The name of the CSS variable to resolve.
* @param baseVars - A record mapping variable names to their values or references.
* @returns The resolved value of the variable, or `undefined` if not found.
*/
const extractVarFromBase = (
varName: string,
baseVars: Record<string, string>
): string | undefined => {
if (baseVars[varName] && baseVars[varName].startsWith("var(")) {
const baseVarName = baseVars[varName]
.substring(6, baseVars[varName].length - 1)
.trim();
return extractVarFromBase(baseVarName, baseVars);
}
return baseVars[varName];
};
/**
* Extracts the value of a CSS custom property (CSS variable) from a given CSSResult object.
*
* @param css - The CSSResult object containing the CSS string to search.
* @param varName - The name of the CSS variable (without the leading '--') to extract.
* @param baseVars - (Optional) A record of base variable names and their values, used to resolve variables that reference other variables via `var()`.
* @returns The value of the CSS variable if found, otherwise an empty string. If the variable references another variable and `baseVars` is provided, attempts to resolve it from `baseVars`.
*/
export const extractVar = (
css: CSSResult,
varName: string,
baseVars?: Record<string, string>
) => {
const cssString = css.toString();
const search = `--${varName}:`;
const startIndex = cssString.indexOf(search);
@ -27,10 +63,17 @@ export const extractVar = (css: CSSResult, varName: string) => {
}
const endIndex = cssString.indexOf(";", startIndex + search.length);
return cssString
const value = cssString
.substring(startIndex + search.length, endIndex)
.replaceAll("}", "")
.trim();
if (baseVars && value.startsWith("var(")) {
const baseVarName = value.substring(6, value.length - 1).trim();
return extractVarFromBase(baseVarName, baseVars) || value;
}
return value;
};
export const extractVars = (css: CSSResult) => {

View File

@ -29,7 +29,7 @@ import { formatTimeLabel } from "./axis-label";
import { ensureArray } from "../../common/array/ensure-array";
import "../chips/ha-assist-chip";
import { downSampleLineData } from "./down-sample";
import { colorVariables } from "../../resources/theme/color.globals";
import { colorVariables } from "../../resources/theme/color/color.globals";
export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000;
const LEGEND_OVERFLOW_LIMIT = 10;

View File

@ -95,6 +95,7 @@ export class HaColorTempSelector extends LitElement {
);
private _valueChanged(ev: CustomEvent) {
ev.stopPropagation();
fireEvent(this, "value-changed", {
value: Number((ev.detail as any).value),
});

View File

@ -279,6 +279,7 @@ export class HaObjectSelector extends LitElement {
}
private _handleChange(ev) {
ev.stopPropagation();
this._valueChangedFromChild = true;
const value = ev.target.value;
if (!ev.target.isValid) {

View File

@ -71,6 +71,7 @@ export class HaTemplateSelector extends LitElement {
}
private _handleChange(ev) {
ev.stopPropagation();
let value = ev.target.value;
if (this.value === value) {
return;

View File

@ -111,6 +111,7 @@ export class HaTextSelector extends LitElement {
}
private _handleChange(ev) {
ev.stopPropagation();
let value = ev.detail?.value ?? ev.target.value;
if (this.value === value) {
return;

View File

@ -33,6 +33,7 @@ export class HaSelectorUiAction extends LitElement {
}
private _valueChanged(ev: CustomEvent) {
ev.stopPropagation();
fireEvent(this, "value-changed", { value: ev.detail.value });
}
}

View File

@ -33,6 +33,7 @@ export class HaSelectorUiColor extends LitElement {
}
private _valueChanged(ev: CustomEvent) {
ev.stopPropagation();
fireEvent(this, "value-changed", { value: ev.detail.value });
}
}

View File

@ -146,7 +146,12 @@ const cloudyStates = new Set<string>([
"lightning-rainy",
]);
const rainStates = new Set<string>(["hail", "rainy", "pouring"]);
const rainStates = new Set<string>([
"hail",
"rainy",
"pouring",
"lightning-rainy",
]);
const windyStates = new Set<string>(["windy", "windy-variant"]);

View File

@ -1,9 +1,19 @@
import { mdiDotsVertical, mdiDownload } from "@mdi/js";
import type { TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../components/ha-button-menu";
import "../../../components/ha-icon-button";
import "../../../components/ha-list-item";
import "../../../components/ha-svg-icon";
import { getSignedPath } from "../../../data/auth";
import "../../../layouts/hass-subpage";
import type { HomeAssistant, Route } from "../../../types";
import "./ha-config-analytics";
import {
downloadFileSupported,
fileDownload,
} from "../../../util/file_download";
@customElement("ha-config-section-analytics")
class HaConfigSectionAnalytics extends LitElement {
@ -21,6 +31,26 @@ class HaConfigSectionAnalytics extends LitElement {
.narrow=${this.narrow}
.header=${this.hass.localize("ui.panel.config.analytics.caption")}
>
${downloadFileSupported(this.hass)
? html`
<ha-button-menu
@action=${this._handleOverflowAction}
slot="toolbar-icon"
>
<ha-icon-button slot="trigger" .path=${mdiDotsVertical}>
</ha-icon-button>
<ha-list-item graphic="icon">
<ha-svg-icon
slot="graphic"
.path=${mdiDownload}
></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.analytics.download_device_info"
)}
</ha-list-item>
</ha-button-menu>
`
: nothing}
<div class="content">
<ha-config-analytics .hass=${this.hass}></ha-config-analytics>
</div>
@ -28,6 +58,11 @@ class HaConfigSectionAnalytics extends LitElement {
`;
}
private async _handleOverflowAction(): Promise<void> {
const signedPath = await getSignedPath(this.hass, "/api/analytics/devices");
fileDownload(signedPath.path);
}
static styles = css`
.content {
padding: 28px 20px 0;

View File

@ -1,9 +1,9 @@
import { mdiPower } from "@mdi/js";
import type { SeriesOption } from "echarts/types/dist/shared";
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import type { SeriesOption } from "echarts/types/dist/shared";
import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { round } from "../../../common/number/round";
@ -11,11 +11,11 @@ import { blankBeforePercent } from "../../../common/translations/blank_before_pe
import "../../../components/buttons/ha-progress-button";
import "../../../components/chart/ha-chart-base";
import "../../../components/ha-alert";
import "../../../components/ha-card";
import "../../../components/ha-button";
import "../../../components/ha-md-list-item";
import "../../../components/ha-card";
import "../../../components/ha-icon-button";
import "../../../components/ha-icon-next";
import "../../../components/ha-md-list-item";
import "../../../components/ha-settings-row";
import type { ConfigEntry } from "../../../data/config_entries";
import { subscribeConfigEntries } from "../../../data/config_entries";
@ -24,6 +24,7 @@ import type {
SystemStatusStreamMessage,
} from "../../../data/hardware";
import { BOARD_NAMES } from "../../../data/hardware";
import { extractApiErrorMessage } from "../../../data/hassio/common";
import type { HassioHassOSInfo } from "../../../data/hassio/host";
import { fetchHassioHassOsInfo } from "../../../data/hassio/host";
import { scanUSBDevices } from "../../../data/usb";
@ -31,13 +32,12 @@ import { showOptionsFlowDialog } from "../../../dialogs/config-flow/show-dialog-
import { showRestartDialog } from "../../../dialogs/restart/show-dialog-restart";
import "../../../layouts/hass-subpage";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { DefaultPrimaryColor } from "../../../resources/theme/color.globals";
import type { ECOption } from "../../../resources/echarts";
import { haStyle } from "../../../resources/styles";
import { DefaultPrimaryColor } from "../../../resources/theme/color/color.globals";
import type { HomeAssistant } from "../../../types";
import { hardwareBrandsUrl } from "../../../util/brands-url";
import { showhardwareAvailableDialog } from "./show-dialog-hardware-available";
import { extractApiErrorMessage } from "../../../data/hassio/common";
import type { ECOption } from "../../../resources/echarts";
const DATASAMPLES = 60;

View File

@ -95,15 +95,12 @@ class AddIntegrationDialog extends LitElement {
public async showDialog(params?: AddIntegrationDialogParams): Promise<void> {
const loadPromise = this._load();
this._open = true;
this._pickedBrand = params?.brand;
this._initialFilter = params?.initialFilter;
this._narrow = matchMedia(
"all and (max-width: 450px), all and (max-height: 500px)"
).matches;
if (params?.domain) {
this._createFlow(params.domain);
// Just open the config flow dialog, do not show this dialog
await this._createFlow(params.domain);
return;
}
if (params?.brand) {
await loadPromise;
const brand = this._integrations?.[params.brand];
@ -111,6 +108,13 @@ class AddIntegrationDialog extends LitElement {
this._fetchFlowsInProgress(Object.keys(brand.integrations));
}
}
// Only open the dialog if no domain is provided
this._open = true;
this._pickedBrand = params?.brand;
this._initialFilter = params?.initialFilter;
this._narrow = matchMedia(
"all and (max-width: 450px), all and (max-height: 500px)"
).matches;
}
public closeDialog() {

View File

@ -1,18 +1,20 @@
import { html, LitElement, css } from "lit";
import type { CSSResultGroup } from "lit";
import { customElement, property, state } from "lit/decorators";
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type {
CallbackDataParams,
TopLevelFormatterParams,
} from "echarts/types/dist/shared";
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type { CSSResultGroup } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import type { HomeAssistant, Route } from "../../../../../types";
import { relativeTime } from "../../../../../common/datetime/relative_time";
import { navigate } from "../../../../../common/navigate";
import { throttle } from "../../../../../common/util/throttle";
import "../../../../../components/chart/ha-network-graph";
import type {
NetworkData,
NetworkNode,
NetworkLink,
NetworkNode,
} from "../../../../../components/chart/ha-network-graph";
import type {
BluetoothDeviceData,
@ -24,11 +26,9 @@ import {
} from "../../../../../data/bluetooth";
import type { DeviceRegistryEntry } from "../../../../../data/device_registry";
import "../../../../../layouts/hass-subpage";
import { colorVariables } from "../../../../../resources/theme/color.globals";
import { navigate } from "../../../../../common/navigate";
import { colorVariables } from "../../../../../resources/theme/color/color.globals";
import type { HomeAssistant, Route } from "../../../../../types";
import { bluetoothAdvertisementMonitorTabs } from "./bluetooth-advertisement-monitor";
import { relativeTime } from "../../../../../common/datetime/relative_time";
import { throttle } from "../../../../../common/util/throttle";
const UPDATE_THROTTLE_TIME = 10000;

View File

@ -1,25 +1,25 @@
import type { CSSResultGroup, PropertyValues } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { mdiRefresh } from "@mdi/js";
import type {
CallbackDataParams,
TopLevelFormatterParams,
} from "echarts/types/dist/shared";
import { mdiRefresh } from "@mdi/js";
import type { CSSResultGroup, PropertyValues } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { navigate } from "../../../../../common/navigate";
import "../../../../../components/chart/ha-network-graph";
import type {
NetworkData,
NetworkNode,
NetworkLink,
NetworkNode,
} from "../../../../../components/chart/ha-network-graph";
import type { ZHADevice } from "../../../../../data/zha";
import { fetchDevices, refreshTopology } from "../../../../../data/zha";
import "../../../../../layouts/hass-tabs-subpage";
import { colorVariables } from "../../../../../resources/theme/color/color.globals";
import type { HomeAssistant, Route } from "../../../../../types";
import { formatAsPaddedHex } from "./functions";
import { zhaTabs } from "./zha-config-dashboard";
import { colorVariables } from "../../../../../resources/theme/color.globals";
import { navigate } from "../../../../../common/navigate";
@customElement("zha-network-visualization-page")
export class ZHANetworkVisualizationPage extends LitElement {

View File

@ -1,33 +1,33 @@
import { customElement, property, state } from "lit/decorators";
import { css, html, LitElement } from "lit";
import memoizeOne from "memoize-one";
import type {
CallbackDataParams,
TopLevelFormatterParams,
} from "echarts/types/dist/shared";
import type { HomeAssistant, Route } from "../../../../../types";
import { configTabs } from "./zwave_js-config-router";
import { SubscribeMixin } from "../../../../../mixins/subscribe-mixin";
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { navigate } from "../../../../../common/navigate";
import { debounce } from "../../../../../common/util/debounce";
import "../../../../../components/chart/ha-network-graph";
import type {
NetworkData,
NetworkLink,
NetworkNode,
} from "../../../../../components/chart/ha-network-graph";
import "../../../../../components/chart/ha-network-graph";
import "../../../../../layouts/hass-tabs-subpage";
import type { DeviceRegistryEntry } from "../../../../../data/device_registry";
import type {
ZWaveJSNodeStatisticsUpdatedMessage,
ZWaveJSNodeStatus,
} from "../../../../../data/zwave_js";
import {
fetchZwaveNetworkStatus,
NodeStatus,
subscribeZwaveNodeStatistics,
} from "../../../../../data/zwave_js";
import type {
ZWaveJSNodeStatisticsUpdatedMessage,
ZWaveJSNodeStatus,
} from "../../../../../data/zwave_js";
import { colorVariables } from "../../../../../resources/theme/color.globals";
import type { DeviceRegistryEntry } from "../../../../../data/device_registry";
import { debounce } from "../../../../../common/util/debounce";
import { navigate } from "../../../../../common/navigate";
import "../../../../../layouts/hass-tabs-subpage";
import { SubscribeMixin } from "../../../../../mixins/subscribe-mixin";
import { colorVariables } from "../../../../../resources/theme/color/color.globals";
import type { HomeAssistant, Route } from "../../../../../types";
import { configTabs } from "./zwave_js-config-router";
@customElement("zwave_js-network-visualization")
export class ZWaveJSNetworkVisualization extends SubscribeMixin(LitElement) {

View File

@ -140,7 +140,7 @@ export class HuiActionEditor extends LitElement {
.value=${action}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidt
naturalMenuWidth
>
<ha-list-item value="default">
${this.hass!.localize(

View File

@ -25,6 +25,7 @@ export interface GUIModeChangedEvent {
export interface ViewEditEvent extends Event {
detail: {
config: LovelaceViewConfig;
valid?: boolean;
};
}

View File

@ -73,6 +73,8 @@ export class HuiDialogEditView extends LitElement {
@state() private _dirty = false;
@state() private _valid = true;
@state() private _yamlMode = false;
@query("ha-yaml-editor") private _editor?: HaYamlEditor;
@ -309,6 +311,7 @@ export class HuiDialogEditView extends LitElement {
?disabled=${!this._config ||
this._saving ||
!this._dirty ||
!this._valid ||
convertToSection ||
convertNotSupported}
@click=${this._save}
@ -580,6 +583,9 @@ export class HuiDialogEditView extends LitElement {
ev.detail.config &&
!deepEqual(this._config, ev.detail.config)
) {
if (ev.detail.valid !== undefined) {
this._valid = ev.detail.valid;
}
this._config = ev.detail.config;
this._dirty = true;
}

View File

@ -23,10 +23,13 @@ declare global {
interface HASSDomEvents {
"view-config-changed": {
config: LovelaceViewConfig;
valid?: boolean;
};
}
}
const VALID_PATH_REGEX = /^[a-zA-Z0-9_-]+$/;
@customElement("hui-view-editor")
export class HuiViewEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@ -35,6 +38,8 @@ export class HuiViewEditor extends LitElement {
@state() private _config!: LovelaceViewConfig;
@state() private _error: Record<string, string> | undefined;
private _suggestedPath = false;
private _schema = memoizeOne(
@ -144,6 +149,8 @@ export class HuiViewEditor extends LitElement {
.schema=${schema}
.computeLabel=${this._computeLabel}
.computeHelper=${this._computeHelper}
.computeError=${this._computeError}
.error=${this._error}
@value-changed=${this._valueChanged}
></ha-form>
`;
@ -168,9 +175,20 @@ export class HuiViewEditor extends LitElement {
config.path = slugify(config.title || "", "-");
}
fireEvent(this, "view-config-changed", { config });
let valid = true;
this._error = undefined;
if (config.path && !VALID_PATH_REGEX.test(config.path)) {
valid = false;
this._error = { path: "error_invalid_path" };
}
fireEvent(this, "view-config-changed", { valid, config });
}
private _computeError = (error: string) =>
this.hass.localize(`ui.panel.lovelace.editor.edit_view.${error}` as any) ||
error;
private _computeLabel = (
schema: SchemaUnion<ReturnType<typeof this._schema>>
) => {
@ -197,6 +215,7 @@ export class HuiViewEditor extends LitElement {
schema: SchemaUnion<ReturnType<typeof this._schema>>
) => {
switch (schema.name) {
case "path":
case "subview":
case "dense_section_placement":
case "top_margin":

View File

@ -1,6 +1,7 @@
import type { PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { normalizeLuminance } from "../../common/color/palette";
import { fireEvent } from "../../common/dom/fire_event";
import "../../components/ha-formfield";
import "../../components/ha-list-item";
@ -13,7 +14,7 @@ import "../../components/ha-textfield";
import {
DefaultAccentColor,
DefaultPrimaryColor,
} from "../../resources/theme/color.globals";
} from "../../resources/theme/color/color.globals";
import type { HomeAssistant } from "../../types";
import { documentationUrl } from "../../util/documentation-url";
@ -174,6 +175,12 @@ export class HaPickThemeRow extends LitElement {
private _handleColorChange(ev: CustomEvent) {
const target = ev.target as any;
// normalize primary color if needed for contrast
if (target.name === "primaryColor") {
target.value = normalizeLuminance(target.value);
}
fireEvent(this, "settheme", { [target.name]: target.value });
}

View File

@ -1,6 +1,6 @@
import { tsParticles } from "@tsparticles/engine";
import { loadLinksPreset } from "@tsparticles/preset-links";
import { DefaultPrimaryColor } from "./theme/color.globals";
import { DefaultPrimaryColor } from "./theme/color/color.globals";
loadLinksPreset(tsParticles).then(() => {
tsParticles.load({

View File

@ -1,21 +1,21 @@
import { css } from "lit";
import {
extractDerivedVars,
extractVar,
extractVars,
} from "../../common/style/derived-css-vars";
} from "../../../common/style/derived-css-vars";
import { coreColorVariables } from "./core.globals";
export const colorStyles = css`
html {
/* text */
--primary-text-color: #212121;
--secondary-text-color: #727272;
--primary-text-color: var(--color-text-primary);
--secondary-text-color: var(--color-text-secondary);
--text-primary-color: #ffffff;
--text-light-primary-color: #212121;
--disabled-text-color: #bdbdbd;
/* main interface colors */
--primary-color: #03a9f4;
--primary-color: var(--color-primary-40);
--dark-primary-color: #0288d1;
--darker-primary-color: #016194;
--light-primary-color: #b3e5fc;
@ -25,7 +25,7 @@ export const colorStyles = css`
--outline-hover-color: rgba(0, 0, 0, 0.24);
/* rgb */
--rgb-primary-color: 3, 169, 244;
--rgb-primary-color: 0, 154, 199;
--rgb-accent-color: 255, 152, 0;
--rgb-primary-text-color: 33, 33, 33;
--rgb-secondary-text-color: 114, 114, 114;
@ -330,7 +330,7 @@ export const colorStyles = css`
}
`;
const darkColorStyles = css`
export const darkColorStyles = css`
html {
--primary-background-color: #111111;
--card-background-color: #1c1c1c;
@ -391,9 +391,11 @@ const darkColorStyles = css`
--ha-button-neutral-light-color: #6a7081;
}
`;
export const colorDerivedVariables = extractDerivedVars(colorStyles);
export const colorVariables = extractVars(colorStyles);
export const darkColorVariables = extractVars(darkColorStyles);
export const DefaultPrimaryColor = extractVar(colorStyles, "primary-color");
export const DefaultPrimaryColor = extractVar(
colorStyles,
"primary-color",
coreColorVariables
);
export const DefaultAccentColor = extractVar(colorStyles, "accent-color");

View File

@ -0,0 +1,154 @@
import { css } from "lit";
import { extractVars } from "../../../common/style/derived-css-vars";
export const coreColorStyles = css`
html {
--white: #ffffff;
--black: #000000;
--transparent-none: rgba(255, 255, 255, 0);
/* primary */
--color-primary-05: #001721;
--color-primary-10: #002e3e;
--color-primary-20: #004156;
--color-primary-30: #006787;
--color-primary-40: #009ac7;
--color-primary-50: #18bcf2;
--color-primary-60: #37c8fd;
--color-primary-70: #7bd4fb;
--color-primary-80: #b9e6fc;
--color-primary-90: #b9e6fc;
--color-primary-95: #eff9fe;
/* neutral */
--color-neutral-05: #101219;
--color-neutral-10: #1b1d26;
--color-neutral-20: #2f323f;
--color-neutral-30: #424554;
--color-neutral-40: #545868;
--color-neutral-50: #717584;
--color-neutral-60: #9194a2;
--color-neutral-70: #abaeb9;
--color-neutral-80: #c7c9d0;
--color-neutral-90: #e4e5e9;
--color-neutral-95: #f1f2f3;
/* indigo */
--color-indigo-05: #0d0a3a;
--color-indigo-10: #181255;
--color-indigo-20: #292381;
--color-indigo-30: #3933a7;
--color-indigo-40: #4945cb;
--color-indigo-50: #6163f2;
--color-indigo-60: #808aff;
--color-indigo-70: #9da9ff;
--color-indigo-80: #bcc7ff;
--color-indigo-90: #dfe5ff;
--color-indigo-95: #f0f2ff;
/* purple */
--color-purple-05: #1e0532;
--color-purple-10: #2d0b48;
--color-purple-20: #491870;
--color-purple-30: #612692;
--color-purple-40: #7936b3;
--color-purple-50: #9951db;
--color-purple-60: #b678f5;
--color-purple-70: #ca99ff;
--color-purple-80: #ddbdff;
--color-purple-90: #eedfff;
--color-purple-95: #f7f0ff;
/* orange */
--color-orange-05: #280700;
--color-orange-10: #3b0f00;
--color-orange-20: #5e1c00;
--color-orange-30: #7e2900;
--color-orange-40: #9d3800;
--color-orange-50: #c94e00;
--color-orange-60: #f36d00;
--color-orange-70: #ff9342;
--color-orange-80: #ffbb89;
--color-orange-90: #ffe0c8;
--color-orange-95: #fff0e4;
/* pink */
--color-pink-05: #28041a;
--color-pink-10: #3c0828;
--color-pink-20: #5e1342;
--color-pink-30: #7d1e58;
--color-pink-40: #9e2a6c;
--color-pink-50: #c84382;
--color-pink-60: #e66ba3;
--color-pink-70: #f78dbf;
--color-pink-80: #fcb5d8;
--color-pink-90: #feddf0;
--color-pink-95: #feeff9;
/* red */
--color-red-05: #2a040b;
--color-red-10: #3e0913;
--color-red-20: #631323;
--color-red-30: #8a132c;
--color-red-40: #b30532;
--color-red-50: #dc3146;
--color-red-60: #f3676c;
--color-red-70: #fd8f90;
--color-red-80: #ffb8b6;
--color-red-90: #ffdedc;
--color-red-95: #fff0ef;
/* yellow */
--color-yellow-05: #220c00;
--color-yellow-10: #331600;
--color-yellow-20: #532600;
--color-yellow-30: #6f3601;
--color-yellow-40: #8c4602;
--color-yellow-50: #b45f04;
--color-yellow-60: #da7e00;
--color-yellow-70: #ef9d00;
--color-yellow-80: #fac22b;
--color-yellow-90: #ffe495;
--color-yellow-95: #fef3cd;
/* green */
--color-green-05: #031608;
--color-green-10: #052310;
--color-green-20: #0a3a1d --color-green-30: #0a5027;
--color-green-40: #036730;
--color-green-50: #00883c;
--color-green-60: #00ac49;
--color-green-70: #5dc36f;
--color-green-80: #93da98;
--color-green-90: #c2f2c1;
--color-green-95: #e3f9e3;
/* cyan */
--color-cyan-05: #00151b;
--color-cyan-10: #002129;
--color-cyan-20: #003844;
--color-cyan-30: #014c5b;
--color-cyan-40: #026274;
--color-cyan-50: #078098;
--color-cyan-60: #00a3c0;
--color-cyan-70: #2fbedc;
--color-cyan-80: #7fd6ec;
--color-cyan-90: #c5ecf7;
--color-cyan-95: #e3f6fb;
/* blue */
--color-blue-05: #000f35;
--color-blue-10: #001a4e;
--color-blue-20: #002d77;
--color-blue-30: #003f9c;
--color-blue-40: #0053c0;
--color-blue-50: #0071ec;
--color-blue-60: #3e96ff;
--color-blue-70: #6eb3ff;
--color-blue-80: #9fceff;
--color-blue-90: #d1e8ff;
--color-blue-95: #e8f3ff;
}
`;
export const coreColorVariables = extractVars(coreColorStyles);

View File

@ -0,0 +1,26 @@
import {
extractDerivedVars,
extractVars,
} from "../../../common/style/derived-css-vars";
import { colorStyles, darkColorStyles } from "./color.globals";
import { coreColorStyles } from "./core.globals";
import {
darkSemanticColorStyles,
semanticColorStyles,
} from "./semantic.globals";
export const darkColorVariables = {
...extractVars(darkColorStyles),
...extractVars(darkSemanticColorStyles),
};
export const colorDerivedVariables = {
...extractDerivedVars(colorStyles),
...extractDerivedVars(semanticColorStyles),
};
export const colorStylesCollection = [
coreColorStyles.toString(),
semanticColorStyles.toString(),
colorStyles.toString(),
];

View File

@ -0,0 +1,353 @@
import { css } from "lit";
export const semanticColorStyles = css`
html {
--color-overlay-modal: rgba(0, 0, 0, 0.25);
--color-focus: var(--color-orange-60);
/* surface */
--color-surface-lower: var(--color-neutral-90);
--color-surface-low: var(--color-neutral-95);
--color-surface-default: var(--white);
/* text */
--color-text-primary: var(--color-neutral-05);
--color-text-secondary: var(--color-neutral-40);
--color-text-disabled: var(--color-neutral-60);
--color-text-link: var(--color-primary-40);
/* text purple */
--color-text-purple-type: var(--color-neutral-05);
--color-text-purple-property: var(--color-purple-40);
--color-text-purple-target: var(--color-primary-40);
/* border primary */
--color-border-quiet: var(--color-primary-80);
--color-border-normal: var(--color-primary-70);
--color-border-loud: var(--color-primary-40);
/* border neutral */
--color-border-neutral-quiet: var(--color-neutral-80);
--color-border-neutral-normal: var(--color-neutral-60);
--color-border-neutral-loud: var(--color-neutral-40);
/* border danger */
--color-border-danger-quiet: var(--color-red-80);
--color-border-danger-normal: var(--color-red-70);
--color-border-danger-loud: var(--color-red-40);
/* border warning */
--color-border-warning-quiet: var(--color-orange-80);
--color-border-warning-normal: var(--color-orange-70);
--color-border-warning-loud: var(--color-orange-40);
/* border success */
--color-border-success-quiet: var(--color-green-80);
--color-border-success-normal: var(--color-green-70);
--color-border-success-loud: var(--color-green-40);
/* border purple */
--color-border-purple-quiet: var(--color-purple-80);
--color-border-purple-normal: var(--color-purple-70);
--color-border-purple-loud: var(--color-purple-40);
/* fill primary quiet */
--color-fill-primary-quiet-resting: var(--color-primary-95);
--color-fill-primary-quiet-hover: var(--color-primary-90);
--color-fill-primary-quiet-active: var(--color-primary-95);
/* fill primary normal */
--color-fill-primary-normal-resting: var(--color-primary-90);
--color-fill-primary-normal-hover: var(--color-primary-80);
--color-fill-primary-normal-active: var(--color-primary-90);
/* fill primary loud */
--color-fill-primary-loud-resting: var(--color-primary-40);
--color-fill-primary-loud-hover: var(--color-primary-30);
--color-fill-primary-loud-active: var(--color-primary-40);
/* fill neutral quiet */
--color-fill-neutral-quiet-resting: var(--color-neutral-95);
--color-fill-neutral-quiet-hover: var(--color-neutral-90);
--color-fill-neutral-quiet-active: var(--color-neutral-95);
/* fill neutral normal */
--color-fill-neutral-normal-resting: var(--color-neutral-90);
--color-fill-neutral-normal-hover: var(--color-neutral-80);
--color-fill-neutral-normal-active: var(--color-neutral-90);
/* fill neutral loud */
--color-fill-neutral-loud-resting: var(--color-neutral-40);
--color-fill-neutral-loud-hover: var(--color-neutral-30);
--color-fill-neutral-loud-active: var(--color-neutral-40);
/* fill disabled quiet */
--color-fill-disabled-quiet-resting: var(--color-neutral-95);
/* fill disabled normal */
--color-fill-disabled-normal-resting: var(--color-neutral-95);
/* fill disabled loud */
--color-fill-disabled-loud-resting: var(--color-neutral-80);
/* fill danger quiet */
--color-fill-danger-quiet-resting: var(--color-red-95);
--color-fill-danger-quiet-hover: var(--color-red-90);
--color-fill-danger-quiet-active: var(--color-red-95);
/* fill danger normal */
--color-fill-danger-normal-resting: var(--color-red-90);
--color-fill-danger-normal-hover: var(--color-red-80);
--color-fill-danger-normal-active: var(--color-red-90);
/* fill danger loud */
--color-fill-danger-loud-resting: var(--color-red-50);
--color-fill-danger-loud-hover: var(--color-red-40);
--color-fill-danger-loud-active: var(--color-red-50);
/* fill warning quiet */
--color-fill-warning-quiet-resting: var(--color-orange-95);
--color-fill-warning-quiet-hover: var(--color-orange-90);
--color-fill-warning-quiet-active: var(--color-orange-95);
/* fill warning normal */
--color-fill-warning-normal-resting: var(--color-orange-90);
--color-fill-warning-normal-hover: var(--color-orange-80);
--color-fill-warning-normal-active: var(--color-orange-90);
/* fill warning loud */
--color-fill-warning-loud-resting: var(--color-orange-70);
--color-fill-warning-loud-hover: var(--color-orange-50);
--color-fill-warning-loud-active: var(--color-orange-70);
/* fill success quiet */
--color-fill-success-quiet-resting: var(--color-green-95);
--color-fill-success-quiet-hover: var(--color-green-90);
--color-fill-success-quiet-active: var(--color-green-95);
/* fill success normal */
--color-fill-success-normal-resting: var(--color-green-90);
--color-fill-success-normal-hover: var(--color-green-80);
--color-fill-success-normal-active: var(--color-green-90);
/* fill success loud */
--color-fill-success-loud-resting: var(--color-green-50);
--color-fill-success-loud-hover: var(--color-green-40);
--color-fill-success-loud-active: var(--color-green-50);
/* fill purple quiet */
--color-fill-purple-quiet-resting: var(--color-purple-95);
--color-fill-purple-quiet-hover: var(--color-purple-90);
--color-fill-purple-quiet-active: var(--color-purple-95);
/* fill purple normal */
--color-fill-purple-normal-resting: var(--color-purple-90);
--color-fill-purple-normal-hover: var(--color-purple-80);
--color-fill-purple-normal-active: var(--color-purple-90);
/* fill purple loud */
--color-fill-purple-loud-resting: var(--color-purple-50);
--color-fill-purple-loud-hover: var(--color-purple-40);
--color-fill-purple-loud-active: var(--color-purple-50);
/* on primary */
--color-on-primary-quiet: var(--color-primary-50);
--color-on-primary-normal: var(--color-primary-40);
--color-on-primary-loud: var(--white);
/* on neutral */
--color-on-neutral-quiet: var(--color-neutral-50);
--color-on-neutral-normal: var(--color-neutral-40);
--color-on-neutral-loud: var(--white);
/* on disabled */
--color-on-disabled-quiet: var(--color-neutral-80);
--color-on-disabled-normal: var(--color-neutral-70);
--color-on-disabled-loud: var(--color-neutral-95);
/* on danger */
--color-on-danger-quiet: var(--color-red-50);
--color-on-danger-normal: var(--color-red-40);
--color-on-danger-loud: var(--white);
/* on warning */
--color-on-warning-quiet: var(--color-orange-50);
--color-on-warning-normal: var(--color-orange-40);
--color-on-warning-loud: var(--white);
/* on success */
--color-on-success-quiet: var(--color-green-50);
--color-on-success-normal: var(--color-green-40);
--color-on-success-loud: var(--white);
/* on purple */
--color-on-purple-quiet: var(--color-purple-30);
--color-on-purple-normal: var(--color-purple-40);
--color-on-purple-loud: var(--white);
/* logo */
--color-logo-primary: var(--color-primary-50);
}
`;
export const darkSemanticColorStyles = css`
html {
/* surface */
--color-surface-lower: var(--black);
--color-surface-low: var(--color-neutral-05);
--color-surface-default: var(--color-neutral-10);
/* text */
--color-text-primary: var(--white);
--color-text-secondary: var(--color-neutral-80);
--color-text-link: var(--color-primary-60);
/* text purple */
--color-text-purple-type: var(--white);
--color-text-purple-property: var(--color-purple-60);
--color-text-purple-target: var(--color-primary-60);
/* border primary */
--color-border-normal: var(--color-primary-50);
/* border neutral */
--color-border-neutral-quiet: var(--color-neutral-40);
--color-border-neutral-normal: var(--color-neutral-50);
--color-border-neutral-loud: var(--color-neutral-70);
/* border danger */
--color-border-danger-normal: var(--color-red-50);
--color-border-danger-loud: var(--color-red-50);
/* border warning */
--color-border-warning-normal: var(--color-orange-50);
--color-border-warning-loud: var(--color-orange-50);
/* border purple */
--color-border-purple-normal: var(--color-purple-50);
--color-border-purple-loud: var(--color-purple-50);
/* fill primary quiet */
--color-fill-primary-quiet-resting: var(--color-primary-05);
--color-fill-primary-quiet-hover: var(--color-primary-10);
--color-fill-primary-quiet-active: var(--color-primary-05);
/* fill primary normal */
--color-fill-primary-normal-resting: var(--color-primary-10);
--color-fill-primary-normal-hover: var(--color-primary-20);
--color-fill-primary-normal-active: var(--color-primary-10);
/* fill neutral quiet */
--color-fill-neutral-quiet-resting: var(--color-neutral-05);
--color-fill-neutral-quiet-hover: var(--color-neutral-10);
--color-fill-neutral-quiet-active: var(--color-neutral-00);
/* fill neutral normal */
--color-fill-neutral-normal-resting: var(--color-neutral-10);
--color-fill-neutral-normal-hover: var(--color-neutral-20);
--color-fill-neutral-normal-active: var(--color-neutral-10);
/* fill disabled quiet */
--color-fill-disabled-quiet-resting: var(--color-neutral-10);
/* fill disabled normal */
--color-fill-disabled-normal-resting: var(--color-neutral-20);
/* fill disabled loud */
--color-fill-disabled-loud-resting: var(--color-neutral-30);
/* fill danger quiet */
--color-fill-danger-quiet-resting: var(--color-red-05);
--color-fill-danger-quiet-hover: var(--color-red-10);
--color-fill-danger-quiet-active: var(--color-red-05);
/* fill danger normal */
--color-fill-danger-normal-resting: var(--color-red-10);
--color-fill-danger-normal-hover: var(--color-red-20);
--color-fill-danger-normal-active: var(--color-red-10);
/* fill danger loud */
--color-fill-danger-loud-resting: var(--color-red-40);
--color-fill-danger-loud-hover: var(--color-red-30);
--color-fill-danger-loud-active: var(--color-red-40);
/* fill warning quiet */
--color-fill-warning-quiet-resting: var(--color-orange-05);
--color-fill-warning-quiet-hover: var(--color-orange-10);
--color-fill-warning-quiet-active: var(--color-orange-05);
/* fill warning normal */
--color-fill-warning-normal-resting: var(--color-orange-10);
--color-fill-warning-normal-hover: var(--color-orange-20);
--color-fill-warning-normal-active: var(--color-orange-10);
/* fill warning loud */
--color-fill-warning-loud-resting: var(--color-orange-40);
--color-fill-warning-loud-hover: var(--color-orange-30);
--color-fill-warning-loud-active: var(--color-orange-40);
/* fill success quiet */
--color-fill-success-quiet-resting: var(--color-green-05);
--color-fill-success-quiet-hover: var(--color-green-10);
--color-fill-success-quiet-active: var(--color-green-05);
/* fill success normal */
--color-fill-success-normal-resting: var(--color-green-10);
--color-fill-success-normal-hover: var(--color-green-20);
--color-fill-success-normal-active: var(--color-green-10);
/* fill success loud */
--color-fill-success-loud-resting: var(--color-green-40);
--color-fill-success-loud-hover: var(--color-green-30);
--color-fill-success-loud-active: var(--color-green-40);
/* fill purple quiet */
--color-fill-purple-quiet-resting: var(--color-purple-05);
--color-fill-purple-quiet-hover: var(--color-purple-10);
--color-fill-purple-quiet-active: var(--color-purple-05);
/* fill purple normal */
--color-fill-purple-normal-resting: var(--color-purple-10);
--color-fill-purple-normal-hover: var(--color-purple-20);
--color-fill-purple-normal-active: var(--color-purple-10);
/* fill purple loud */
--color-fill-purple-loud-resting: var(--color-purple-40);
--color-fill-purple-loud-hover: var(--color-purple-30);
--color-fill-purple-loud-active: var(--color-purple-40);
/* on primary */
--color-on-primary-quiet: var(--color-primary-70);
--color-on-primary-normal: var(--color-primary-80);
/* on neutral */
--color-on-neutral-quiet: var(--color-neutral-70);
--color-on-neutral-normal: var(--color-neutral-60);
--color-on-neutral-loud: var(--white);
/* on disabled */
--color-on-disabled-quiet: var(--color-neutral-40);
--color-on-disabled-normal: var(--color-neutral-50);
--color-on-disabled-loud: var(--color-neutral-50);
/* on danger */
--color-on-danger-quiet: var(--color-red-70);
--color-on-danger-normal: var(--color-red-60);
--color-on-danger-loud: var(--white);
/* on warning */
--color-on-warning-quiet: var(--color-orange-70);
--color-on-warning-normal: var(--color-orange-60);
--color-on-warning-loud: var(--white);
/* on success */
--color-on-success-quiet: var(--color-green-70);
--color-on-success-normal: var(--color-green-60);
--color-on-success-loud: var(--white);
/* on purple */
--color-on-purple-quiet: var(--color-purple-70);
--color-on-purple-normal: var(--color-purple-60);
--color-on-purple-loud: var(--white);
}
`;

View File

@ -1,5 +1,5 @@
import { fontStyles } from "../roboto";
import { colorDerivedVariables, colorStyles } from "./color.globals";
import { colorDerivedVariables, colorStylesCollection } from "./color";
import { mainDerivedVariables, mainStyles } from "./main.globals";
import {
typographyDerivedVariables,
@ -9,7 +9,7 @@ import {
export const themeStyles = [
mainStyles.toString(),
typographyStyles.toString(),
colorStyles.toString(),
...colorStylesCollection,
fontStyles.toString(),
].join("");

View File

@ -6541,7 +6541,8 @@
},
"need_base_enabled": "You need to enable basic analytics for this option to be available",
"learn_more": "How we process your data",
"intro": "Share anonymized information from your installation to help make Home Assistant better and help us convince manufacturers to add local control and privacy-focused features."
"intro": "Share anonymized information from your installation to help make Home Assistant better and help us convince manufacturers to add local control and privacy-focused features.",
"download_device_info": "Preview device analytics"
},
"network": {
"caption": "Network",
@ -7028,10 +7029,12 @@
"top_margin": "Add additional space above",
"top_margin_helper": "Helps reveal more of the background",
"subview_helper": "Subviews don't appear in tabs and have a back button.",
"path_helper": "This value will become part of the URL path to open this view.",
"edit_ui": "Edit in visual editor",
"edit_yaml": "Edit in YAML",
"saving_failed": "Saving failed",
"error_same_url": "You cannot save a view with the same URL as a different existing view.",
"error_invalid_path": "URL contains invalid/reserved characters. Please enter a simple string only for the path of this view.",
"move_to_dashboard": "Move to dashboard"
},
"edit_view_header": {
@ -8849,7 +8852,7 @@
"uploading": "[%key:ui::components::file-upload::uploading%]",
"details": {
"home_assistant_missing": "This backup does not include your Home Assistant configuration, you cannot use it to restore your instance.",
"addons_unsupported": "Your installation method doesnt support add-ons. If you want to restore these, you have to install Home Assistant Operating System",
"addons_unsupported": "Your installation method doesnt support add-ons. If you want to restore these, you have to install Home Assistant Operating System",
"summary": {
"created": "[%key:ui::panel::config::backup::details::summary::created%]",
"content": "Content"

1308
yarn.lock

File diff suppressed because it is too large Load Diff