mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-05 00:49:53 +00:00
Compare commits
7 Commits
card-visib
...
dropdown
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b70d1492b3 | ||
|
|
fba38cc6ec | ||
|
|
a8b0291a9a | ||
|
|
a240bd2634 | ||
|
|
9d4bf30753 | ||
|
|
10b99433ea | ||
|
|
f0d4c9cb72 |
55
gallery/src/pages/components/ha-dropdown.markdown
Normal file
55
gallery/src/pages/components/ha-dropdown.markdown
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
title: Dropdown
|
||||
---
|
||||
|
||||
# Dropdown `<ha-dropdown>`
|
||||
|
||||
## Implementation
|
||||
|
||||
A compact, accessible dropdown menu for choosing actions or settings. `ha-dropdown` supports composed menu items (`<ha-dropdown-item>`) for icons, submenus, checkboxes, disabled entries, and destructive variants. Use composition with `slot="trigger"` to control the trigger button and use `<ha-dropdown-item>` for rich item content.
|
||||
|
||||
### Example usage (composition)
|
||||
|
||||
```html
|
||||
<ha-dropdown open>
|
||||
<ha-button slot="trigger" with-caret>Dropdown</ha-button>
|
||||
|
||||
<ha-dropdown-item>
|
||||
<ha-svg-icon .path="${mdiContentCut}" slot="icon"></ha-svg-icon>
|
||||
Cut
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-dropdown-item>
|
||||
<ha-svg-icon .path="${mdiContentCopy}" slot="icon"></ha-svg-icon>
|
||||
Copy
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-dropdown-item disabled>
|
||||
<ha-svg-icon .path="${mdiContentPaste}" slot="icon"></ha-svg-icon>
|
||||
Paste
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-dropdown-item>
|
||||
Show images
|
||||
<ha-dropdown-item slot="submenu" value="show-all-images"
|
||||
>Show all images</ha-dropdown-item
|
||||
>
|
||||
<ha-dropdown-item slot="submenu" value="show-thumbnails"
|
||||
>Show thumbnails</ha-dropdown-item
|
||||
>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-dropdown-item type="checkbox" checked>Emoji shortcuts</ha-dropdown-item>
|
||||
<ha-dropdown-item type="checkbox" checked>Word wrap</ha-dropdown-item>
|
||||
|
||||
<ha-dropdown-item variant="danger">
|
||||
<ha-svg-icon .path="${mdiDelete}" slot="icon"></ha-svg-icon>
|
||||
Delete
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
```
|
||||
|
||||
### API
|
||||
|
||||
This component is based on the webawesome dropdown component.
|
||||
Check the [webawesome documentation](https://webawesome.com/docs/components/dropdown/) for more details.
|
||||
133
gallery/src/pages/components/ha-dropdown.ts
Normal file
133
gallery/src/pages/components/ha-dropdown.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import {
|
||||
mdiContentCopy,
|
||||
mdiContentCut,
|
||||
mdiContentPaste,
|
||||
mdiDelete,
|
||||
} from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import "../../../../src/components/ha-button";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import "../../../../src/components/ha-dropdown-item";
|
||||
import "@home-assistant/webawesome/dist/components/icon/icon";
|
||||
import "@home-assistant/webawesome/dist/components/button/button";
|
||||
import "@home-assistant/webawesome/dist/components/dropdown/dropdown";
|
||||
import "../../../../src/components/ha-dropdown";
|
||||
import "@home-assistant/webawesome/dist/components/popup/popup";
|
||||
import { applyThemesOnElement } from "../../../../src/common/dom/apply_themes_on_element";
|
||||
import "../../../../src/components/ha-icon-button";
|
||||
|
||||
@customElement("demo-components-ha-dropdown")
|
||||
export class DemoHaDropdown extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${["light", "dark"].map(
|
||||
(mode) => html`
|
||||
<div class=${mode}>
|
||||
<ha-card header="ha-button in ${mode}">
|
||||
<div class="card-content">
|
||||
<ha-dropdown open>
|
||||
<ha-button slot="trigger" with-caret>Dropdown</ha-button>
|
||||
|
||||
<ha-dropdown-item>
|
||||
<ha-svg-icon
|
||||
.path=${mdiContentCut}
|
||||
slot="icon"
|
||||
></ha-svg-icon>
|
||||
Cut
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item>
|
||||
<ha-svg-icon
|
||||
.path=${mdiContentCopy}
|
||||
slot="icon"
|
||||
></ha-svg-icon>
|
||||
Copy
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item disabled>
|
||||
<ha-svg-icon
|
||||
.path=${mdiContentPaste}
|
||||
slot="icon"
|
||||
></ha-svg-icon>
|
||||
Paste
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item>
|
||||
Show images
|
||||
<ha-dropdown-item slot="submenu" value="show-all-images"
|
||||
>Show All Images</ha-dropdown-item
|
||||
>
|
||||
<ha-dropdown-item slot="submenu" value="show-thumbnails"
|
||||
>Show Thumbnails</ha-dropdown-item
|
||||
>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item type="checkbox" checked
|
||||
>Emoji Shortcuts</ha-dropdown-item
|
||||
>
|
||||
<ha-dropdown-item type="checkbox" checked
|
||||
>Word Wrap</ha-dropdown-item
|
||||
>
|
||||
<ha-dropdown-item variant="danger">
|
||||
<ha-svg-icon .path=${mdiDelete} slot="icon"></ha-svg-icon>
|
||||
Delete
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
</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: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.dark,
|
||||
.light {
|
||||
display: block;
|
||||
background-color: var(--primary-background-color);
|
||||
padding: 0 50px;
|
||||
}
|
||||
.button {
|
||||
padding: unset;
|
||||
}
|
||||
ha-card {
|
||||
margin: 24px auto;
|
||||
}
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
.card-content div {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-components-ha-dropdown": DemoHaDropdown;
|
||||
}
|
||||
}
|
||||
@@ -111,7 +111,7 @@
|
||||
"fuse.js": "7.1.0",
|
||||
"google-timezones-json": "1.2.0",
|
||||
"gulp-zopfli-green": "6.0.2",
|
||||
"hls.js": "1.6.14",
|
||||
"hls.js": "1.6.13",
|
||||
"home-assistant-js-websocket": "9.5.0",
|
||||
"idb-keyval": "6.2.2",
|
||||
"intl-messageformat": "10.7.18",
|
||||
@@ -156,7 +156,7 @@
|
||||
"@lokalise/node-api": "15.3.1",
|
||||
"@octokit/auth-oauth-device": "8.0.3",
|
||||
"@octokit/plugin-retry": "8.0.3",
|
||||
"@octokit/rest": "22.0.1",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@rsdoctor/rspack-plugin": "1.3.7",
|
||||
"@rspack/core": "1.6.0",
|
||||
"@rspack/dev-server": "1.1.4",
|
||||
@@ -231,7 +231,7 @@
|
||||
"clean-css": "5.3.3",
|
||||
"@lit/reactive-element": "2.1.1",
|
||||
"@fullcalendar/daygrid": "6.1.19",
|
||||
"globals": "16.5.0",
|
||||
"globals": "16.4.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"
|
||||
},
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
import { TZDate } from "@date-fns/tz";
|
||||
import { isBefore, isAfter, isWithinInterval } from "date-fns";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { TimeZone } from "../../data/translation";
|
||||
import { WEEKDAY_MAP, type WeekdayShort } from "./weekday";
|
||||
|
||||
/**
|
||||
* Parse a time string (HH:MM or HH:MM:SS) and set it on today's date in the given timezone
|
||||
* @param timeString The time string to parse
|
||||
* @param timezone The timezone to use
|
||||
* @returns The Date object
|
||||
*/
|
||||
const parseTimeString = (timeString: string, timezone: string): Date => {
|
||||
const parts = timeString.split(":");
|
||||
|
||||
if (parts.length < 2 || parts.length > 3) {
|
||||
throw new Error(
|
||||
`Invalid time format: ${timeString}. Expected HH:MM or HH:MM:SS`
|
||||
);
|
||||
}
|
||||
|
||||
const hours = parseInt(parts[0], 10);
|
||||
const minutes = parseInt(parts[1], 10);
|
||||
const seconds = parts.length === 3 ? parseInt(parts[2], 10) : 0;
|
||||
|
||||
if (isNaN(hours) || isNaN(minutes) || isNaN(seconds)) {
|
||||
throw new Error(`Invalid time values in: ${timeString}`);
|
||||
}
|
||||
|
||||
// Add range validation
|
||||
if (hours < 0 || hours > 23) {
|
||||
throw new Error(`Invalid hours in: ${timeString}. Must be 0-23`);
|
||||
}
|
||||
if (minutes < 0 || minutes > 59) {
|
||||
throw new Error(`Invalid minutes in: ${timeString}. Must be 0-59`);
|
||||
}
|
||||
if (seconds < 0 || seconds > 59) {
|
||||
throw new Error(`Invalid seconds in: ${timeString}. Must be 0-59`);
|
||||
}
|
||||
|
||||
const now = new TZDate(new Date(), timezone);
|
||||
const dateWithTime = new TZDate(
|
||||
now.getFullYear(),
|
||||
now.getMonth(),
|
||||
now.getDate(),
|
||||
hours,
|
||||
minutes,
|
||||
seconds,
|
||||
0,
|
||||
timezone
|
||||
);
|
||||
|
||||
return new Date(dateWithTime.getTime());
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the current time matches the time condition (after/before/weekday)
|
||||
* @param hass Home Assistant object
|
||||
* @param after Optional time string (HH:MM) for after condition
|
||||
* @param before Optional time string (HH:MM) for before condition
|
||||
* @param weekdays Optional array of weekdays to match
|
||||
* @returns true if current time matches the condition
|
||||
*/
|
||||
export const checkTimeInRange = (
|
||||
hass: HomeAssistant,
|
||||
after?: string,
|
||||
before?: string,
|
||||
weekdays?: WeekdayShort[]
|
||||
): boolean => {
|
||||
const timezone =
|
||||
hass.locale.time_zone === TimeZone.server
|
||||
? hass.config.time_zone
|
||||
: Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
|
||||
const now = new TZDate(new Date(), timezone);
|
||||
|
||||
// Check weekday condition
|
||||
if (weekdays && weekdays.length > 0) {
|
||||
const currentWeekday = WEEKDAY_MAP[now.getDay()];
|
||||
if (!weekdays.includes(currentWeekday)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check time conditions
|
||||
if (!after && !before) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const afterDate = after ? parseTimeString(after, timezone) : undefined;
|
||||
const beforeDate = before ? parseTimeString(before, timezone) : undefined;
|
||||
|
||||
if (afterDate && beforeDate) {
|
||||
if (isBefore(beforeDate, afterDate)) {
|
||||
// Crosses midnight (e.g., 22:00 to 06:00)
|
||||
return isAfter(now, afterDate) || isBefore(now, beforeDate);
|
||||
}
|
||||
return isWithinInterval(now, { start: afterDate, end: beforeDate });
|
||||
}
|
||||
|
||||
if (afterDate) {
|
||||
return isAfter(now, afterDate) || now.getTime() === afterDate.getTime();
|
||||
}
|
||||
|
||||
if (beforeDate) {
|
||||
return isBefore(now, beforeDate) || now.getTime() === beforeDate.getTime();
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
@@ -1,7 +1,18 @@
|
||||
import { getWeekStartByLocale } from "weekstart";
|
||||
import type { FrontendLocaleData } from "../../data/translation";
|
||||
import { FirstWeekday } from "../../data/translation";
|
||||
import { WEEKDAYS_LONG, type WeekdayIndex } from "./weekday";
|
||||
|
||||
export const weekdays = [
|
||||
"sunday",
|
||||
"monday",
|
||||
"tuesday",
|
||||
"wednesday",
|
||||
"thursday",
|
||||
"friday",
|
||||
"saturday",
|
||||
] as const;
|
||||
|
||||
type WeekdayIndex = 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
||||
|
||||
export const firstWeekdayIndex = (locale: FrontendLocaleData): WeekdayIndex => {
|
||||
if (locale.first_weekday === FirstWeekday.language) {
|
||||
@@ -12,12 +23,12 @@ export const firstWeekdayIndex = (locale: FrontendLocaleData): WeekdayIndex => {
|
||||
}
|
||||
return (getWeekStartByLocale(locale.language) % 7) as WeekdayIndex;
|
||||
}
|
||||
return WEEKDAYS_LONG.includes(locale.first_weekday)
|
||||
? (WEEKDAYS_LONG.indexOf(locale.first_weekday) as WeekdayIndex)
|
||||
return weekdays.includes(locale.first_weekday)
|
||||
? (weekdays.indexOf(locale.first_weekday) as WeekdayIndex)
|
||||
: 1;
|
||||
};
|
||||
|
||||
export const firstWeekday = (locale: FrontendLocaleData) => {
|
||||
const index = firstWeekdayIndex(locale);
|
||||
return WEEKDAYS_LONG[index];
|
||||
return weekdays[index];
|
||||
};
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
export type WeekdayIndex = 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
||||
|
||||
export type WeekdayShort =
|
||||
| "sun"
|
||||
| "mon"
|
||||
| "tue"
|
||||
| "wed"
|
||||
| "thu"
|
||||
| "fri"
|
||||
| "sat";
|
||||
|
||||
export type WeekdayLong =
|
||||
| "sunday"
|
||||
| "monday"
|
||||
| "tuesday"
|
||||
| "wednesday"
|
||||
| "thursday"
|
||||
| "friday"
|
||||
| "saturday";
|
||||
|
||||
export const WEEKDAYS_SHORT = [
|
||||
"sun",
|
||||
"mon",
|
||||
"tue",
|
||||
"wed",
|
||||
"thu",
|
||||
"fri",
|
||||
"sat",
|
||||
] as const satisfies readonly WeekdayShort[];
|
||||
|
||||
export const WEEKDAYS_LONG = [
|
||||
"sunday",
|
||||
"monday",
|
||||
"tuesday",
|
||||
"wednesday",
|
||||
"thursday",
|
||||
"friday",
|
||||
"saturday",
|
||||
] as const satisfies readonly WeekdayLong[];
|
||||
|
||||
export const WEEKDAY_MAP = {
|
||||
0: "sun",
|
||||
1: "mon",
|
||||
2: "tue",
|
||||
3: "wed",
|
||||
4: "thu",
|
||||
5: "fri",
|
||||
6: "sat",
|
||||
} as const satisfies Record<WeekdayIndex, WeekdayShort>;
|
||||
|
||||
export const WEEKDAY_SHORT_TO_LONG = {
|
||||
sun: "sunday",
|
||||
mon: "monday",
|
||||
tue: "tuesday",
|
||||
wed: "wednesday",
|
||||
thu: "thursday",
|
||||
fri: "friday",
|
||||
sat: "saturday",
|
||||
} as const satisfies Record<WeekdayShort, WeekdayLong>;
|
||||
@@ -9,9 +9,9 @@ type EntityCategory = "none" | "config" | "diagnostic";
|
||||
export interface EntityFilter {
|
||||
domain?: string | string[];
|
||||
device_class?: string | string[];
|
||||
device?: string | null | (string | null)[];
|
||||
area?: string | null | (string | null)[];
|
||||
floor?: string | null | (string | null)[];
|
||||
device?: string | string[];
|
||||
area?: string | string[];
|
||||
floor?: string | string[];
|
||||
label?: string | string[];
|
||||
entity_category?: EntityCategory | EntityCategory[];
|
||||
hidden_platform?: string | string[];
|
||||
@@ -19,18 +19,6 @@ export interface EntityFilter {
|
||||
|
||||
export type EntityFilterFunc = (entityId: string) => boolean;
|
||||
|
||||
const normalizeFilterArray = <T>(
|
||||
value: T | null | T[] | (T | null)[] | undefined
|
||||
): Set<T | null> | undefined => {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (value === null) {
|
||||
return new Set([null]);
|
||||
}
|
||||
return new Set(ensureArray(value));
|
||||
};
|
||||
|
||||
export const generateEntityFilter = (
|
||||
hass: HomeAssistant,
|
||||
filter: EntityFilter
|
||||
@@ -41,9 +29,11 @@ export const generateEntityFilter = (
|
||||
const deviceClasses = filter.device_class
|
||||
? new Set(ensureArray(filter.device_class))
|
||||
: undefined;
|
||||
const floors = normalizeFilterArray(filter.floor);
|
||||
const areas = normalizeFilterArray(filter.area);
|
||||
const devices = normalizeFilterArray(filter.device);
|
||||
const floors = filter.floor ? new Set(ensureArray(filter.floor)) : undefined;
|
||||
const areas = filter.area ? new Set(ensureArray(filter.area)) : undefined;
|
||||
const devices = filter.device
|
||||
? new Set(ensureArray(filter.device))
|
||||
: undefined;
|
||||
const entityCategories = filter.entity_category
|
||||
? new Set(ensureArray(filter.entity_category))
|
||||
: undefined;
|
||||
@@ -83,20 +73,23 @@ export const generateEntityFilter = (
|
||||
}
|
||||
|
||||
if (floors) {
|
||||
const floorId = floor?.floor_id ?? null;
|
||||
if (!floors.has(floorId)) {
|
||||
if (!floor || !floors.has(floor.floor_id)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (areas) {
|
||||
const areaId = area?.area_id ?? null;
|
||||
if (!areas.has(areaId)) {
|
||||
if (!area) {
|
||||
return false;
|
||||
}
|
||||
if (!areas.has(area.area_id)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (devices) {
|
||||
const deviceId = device?.id ?? null;
|
||||
if (!devices.has(deviceId)) {
|
||||
if (!device) {
|
||||
return false;
|
||||
}
|
||||
if (!devices.has(device.id)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
33
src/components/ha-dropdown-item.ts
Normal file
33
src/components/ha-dropdown-item.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import DropdownItem from "@home-assistant/webawesome/dist/components/dropdown-item/dropdown-item";
|
||||
import { css, type CSSResultGroup } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
|
||||
/**
|
||||
* Home Assistant dropdown item component
|
||||
*
|
||||
* @element ha-dropdown-item
|
||||
* @extends {DropdownItem}
|
||||
*
|
||||
* @summary
|
||||
* A stylable dropdown item component supporting Home Assistant theming, variants, and appearances based on webawesome dropdown item.
|
||||
*
|
||||
*/
|
||||
@customElement("ha-dropdown-item")
|
||||
export class HaDropdownItem extends DropdownItem {
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
DropdownItem.styles,
|
||||
css`
|
||||
:host {
|
||||
min-height: 40px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-dropdown-item": HaDropdownItem;
|
||||
}
|
||||
}
|
||||
45
src/components/ha-dropdown.ts
Normal file
45
src/components/ha-dropdown.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import Dropdown from "@home-assistant/webawesome/dist/components/dropdown/dropdown";
|
||||
import { css, type CSSResultGroup } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
|
||||
/**
|
||||
* Home Assistant dropdown component
|
||||
*
|
||||
* @element ha-dropdown
|
||||
* @extends {Dropdown}
|
||||
*
|
||||
* @summary
|
||||
* A stylable dropdown component supporting Home Assistant theming, variants, and appearances based on webawesome dropdown.
|
||||
*
|
||||
*/
|
||||
@customElement("ha-dropdown")
|
||||
export class HaDropdown extends Dropdown {
|
||||
@property({ attribute: false }) dropdownTag = "ha-dropdown";
|
||||
|
||||
@property({ attribute: false }) dropdownItemTag = "ha-dropdown-item";
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
Dropdown.styles,
|
||||
css`
|
||||
:host {
|
||||
--wa-color-surface-border: var(--ha-color-border-normal);
|
||||
--wa-color-surface-raised: var(
|
||||
--card-background-color,
|
||||
var(--ha-dialog-surface-background, var(--mdc-theme-surface, #fff)),
|
||||
);
|
||||
}
|
||||
|
||||
#menu {
|
||||
--wa-shadow-m: 0px 4px 8px 0px var(--ha-color-shadow);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-dropdown": HaDropdown;
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ export class HaTooltip extends Tooltip {
|
||||
@property({ attribute: "show-delay", type: Number }) showDelay = 150;
|
||||
|
||||
/** The amount of time to wait before hiding the tooltip when the user mouses out.. */
|
||||
@property({ attribute: "hide-delay", type: Number }) hideDelay = 150;
|
||||
@property({ attribute: "hide-delay", type: Number }) hideDelay = 400;
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
|
||||
@@ -12,7 +12,6 @@ import { CONDITION_BUILDING_BLOCKS } from "./condition";
|
||||
import type { DeviceCondition, DeviceTrigger } from "./device_automation";
|
||||
import type { Action, Field, MODES } from "./script";
|
||||
import { migrateAutomationAction } from "./script";
|
||||
import type { WeekdayShort } from "../common/datetime/weekday";
|
||||
|
||||
export const AUTOMATION_DEFAULT_MODE: (typeof MODES)[number] = "single";
|
||||
export const AUTOMATION_DEFAULT_MAX = 10;
|
||||
@@ -258,11 +257,13 @@ export interface ZoneCondition extends BaseCondition {
|
||||
zone: string;
|
||||
}
|
||||
|
||||
type Weekday = "sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat";
|
||||
|
||||
export interface TimeCondition extends BaseCondition {
|
||||
condition: "time";
|
||||
after?: string;
|
||||
before?: string;
|
||||
weekday?: WeekdayShort | WeekdayShort[];
|
||||
weekday?: Weekday | Weekday[];
|
||||
}
|
||||
|
||||
export interface TemplateCondition extends BaseCondition {
|
||||
|
||||
@@ -115,24 +115,6 @@ const processAreasForClimate = (
|
||||
return cards;
|
||||
};
|
||||
|
||||
const processUnassignedEntities = (
|
||||
hass: HomeAssistant,
|
||||
entities: string[]
|
||||
): LovelaceCardConfig[] => {
|
||||
const unassignedFilter = generateEntityFilter(hass, {
|
||||
area: null,
|
||||
});
|
||||
const unassignedEntities = entities.filter(unassignedFilter);
|
||||
const areaCards: LovelaceCardConfig[] = [];
|
||||
const computeTileCard = computeAreaTileCardConfig(hass, "", true);
|
||||
|
||||
for (const entityId of unassignedEntities) {
|
||||
areaCards.push(computeTileCard(entityId));
|
||||
}
|
||||
|
||||
return areaCards;
|
||||
};
|
||||
|
||||
@customElement("climate-view-strategy")
|
||||
export class ClimateViewStrategy extends ReactiveElement {
|
||||
static async generate(
|
||||
@@ -208,33 +190,10 @@ export class ClimateViewStrategy extends ReactiveElement {
|
||||
}
|
||||
}
|
||||
|
||||
// Process unassigned entities
|
||||
const unassignedCards = processUnassignedEntities(hass, entities);
|
||||
|
||||
if (unassignedCards.length > 0) {
|
||||
const section: LovelaceSectionRawConfig = {
|
||||
type: "grid",
|
||||
column_span: 2,
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading:
|
||||
sections.length > 0
|
||||
? hass.localize(
|
||||
"ui.panel.lovelace.strategy.climate.other_devices"
|
||||
)
|
||||
: hass.localize("ui.panel.lovelace.strategy.climate.devices"),
|
||||
},
|
||||
...unassignedCards,
|
||||
],
|
||||
};
|
||||
sections.push(section);
|
||||
}
|
||||
|
||||
return {
|
||||
type: "sections",
|
||||
max_columns: 2,
|
||||
sections: sections,
|
||||
sections: sections || [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { customElement, property, query, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../../../../common/config/is_component_loaded";
|
||||
import { dynamicElement } from "../../../../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-button";
|
||||
import type { ExtEntityRegistryEntry } from "../../../../../data/entity_registry";
|
||||
import { removeEntityRegistryEntry } from "../../../../../data/entity_registry";
|
||||
import { HELPERS_CRUD } from "../../../../../data/helpers_crud";
|
||||
@@ -23,6 +22,7 @@ import "../../../helpers/forms/ha-schedule-form";
|
||||
import "../../../helpers/forms/ha-timer-form";
|
||||
import "../../../voice-assistants/entity-voice-settings";
|
||||
import "../../entity-registry-settings-editor";
|
||||
import "../../../../../components/ha-button";
|
||||
import type { EntityRegistrySettingsEditor } from "../../entity-registry-settings-editor";
|
||||
|
||||
@customElement("entity-settings-helper-tab")
|
||||
@@ -72,28 +72,22 @@ export class EntitySettingsHelperTab extends LitElement {
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
${this._item === null
|
||||
? html`<ha-alert alert-type="info"
|
||||
>${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.yaml_not_editable"
|
||||
)}</ha-alert
|
||||
>`
|
||||
: nothing}
|
||||
${!this._componentLoaded
|
||||
? this.hass.localize(
|
||||
"ui.dialogs.helper_settings.platform_not_loaded",
|
||||
{ platform: this.entry.platform }
|
||||
)
|
||||
: html`
|
||||
<span @value-changed=${this._valueChanged}>
|
||||
${dynamicElement(`ha-${this.entry.platform}-form`, {
|
||||
hass: this.hass,
|
||||
item: this._item,
|
||||
entry: this.entry,
|
||||
disabled: this._item === null,
|
||||
})}
|
||||
</span>
|
||||
`}
|
||||
: this._item === null
|
||||
? this.hass.localize("ui.dialogs.helper_settings.yaml_not_editable")
|
||||
: html`
|
||||
<span @value-changed=${this._valueChanged}>
|
||||
${dynamicElement(`ha-${this.entry.platform}-form`, {
|
||||
hass: this.hass,
|
||||
item: this._item,
|
||||
entry: this.entry,
|
||||
})}
|
||||
</span>
|
||||
`}
|
||||
<entity-registry-settings-editor
|
||||
.hass=${this.hass}
|
||||
.entry=${this.entry}
|
||||
@@ -128,9 +122,6 @@ export class EntitySettingsHelperTab extends LitElement {
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
if (this._item === null) {
|
||||
return;
|
||||
}
|
||||
this._error = undefined;
|
||||
this._item = ev.detail.value;
|
||||
}
|
||||
@@ -204,10 +195,6 @@ export class EntitySettingsHelperTab extends LitElement {
|
||||
display: block;
|
||||
padding: 0 !important;
|
||||
}
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin-bottom: var(--ha-space-4);
|
||||
}
|
||||
.form {
|
||||
padding: 20px 24px;
|
||||
}
|
||||
|
||||
@@ -784,7 +784,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
<ha-labels-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._labels}
|
||||
.disabled=${!!this.disabled}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._labelsChanged}
|
||||
></ha-labels-picker>
|
||||
${this._cameraPrefs
|
||||
|
||||
@@ -17,8 +17,6 @@ class HaCounterForm extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public new = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
private _item?: Partial<Counter>;
|
||||
|
||||
@state() private _name!: string;
|
||||
@@ -84,7 +82,6 @@ class HaCounterForm extends LitElement {
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}
|
||||
dialogInitialFocus
|
||||
.disabled=${this.disabled}
|
||||
></ha-textfield>
|
||||
<ha-icon-picker
|
||||
.hass=${this.hass}
|
||||
@@ -94,7 +91,6 @@ class HaCounterForm extends LitElement {
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-icon-picker>
|
||||
<ha-textfield
|
||||
.value=${this._minimum}
|
||||
@@ -104,7 +100,6 @@ class HaCounterForm extends LitElement {
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.counter.minimum"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-textfield>
|
||||
<ha-textfield
|
||||
.value=${this._maximum}
|
||||
@@ -114,7 +109,6 @@ class HaCounterForm extends LitElement {
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.counter.maximum"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-textfield>
|
||||
<ha-textfield
|
||||
.value=${this._initial}
|
||||
@@ -124,7 +118,6 @@ class HaCounterForm extends LitElement {
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.counter.initial"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-textfield>
|
||||
<ha-expansion-panel
|
||||
header=${this.hass.localize(
|
||||
@@ -140,14 +133,12 @@ class HaCounterForm extends LitElement {
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.counter.step"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-textfield>
|
||||
<div class="row">
|
||||
<ha-switch
|
||||
.checked=${this._restore}
|
||||
.configValue=${"restore"}
|
||||
@change=${this._valueChanged}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
</ha-switch>
|
||||
<div>
|
||||
|
||||
@@ -14,8 +14,6 @@ class HaInputBooleanForm extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public new = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
private _item?: InputBoolean;
|
||||
|
||||
@state() private _name!: string;
|
||||
@@ -61,7 +59,6 @@ class HaInputBooleanForm extends LitElement {
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}
|
||||
dialogInitialFocus
|
||||
.disabled=${this.disabled}
|
||||
></ha-textfield>
|
||||
<ha-icon-picker
|
||||
.hass=${this.hass}
|
||||
@@ -71,7 +68,6 @@ class HaInputBooleanForm extends LitElement {
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-icon-picker>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -14,8 +14,6 @@ class HaInputButtonForm extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public new = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@state() private _name!: string;
|
||||
|
||||
@state() private _icon!: string;
|
||||
@@ -61,7 +59,6 @@ class HaInputButtonForm extends LitElement {
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}
|
||||
dialogInitialFocus
|
||||
.disabled=${this.disabled}
|
||||
></ha-textfield>
|
||||
<ha-icon-picker
|
||||
.hass=${this.hass}
|
||||
@@ -71,7 +68,6 @@ class HaInputButtonForm extends LitElement {
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-icon-picker>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -17,8 +17,6 @@ class HaInputDateTimeForm extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public new = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
private _item?: InputDateTime;
|
||||
|
||||
@state() private _name!: string;
|
||||
@@ -75,7 +73,6 @@ class HaInputDateTimeForm extends LitElement {
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}
|
||||
dialogInitialFocus
|
||||
.disabled=${this.disabled}
|
||||
></ha-textfield>
|
||||
<ha-icon-picker
|
||||
.hass=${this.hass}
|
||||
@@ -85,7 +82,6 @@ class HaInputDateTimeForm extends LitElement {
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-icon-picker>
|
||||
<br />
|
||||
${this.hass.localize("ui.dialogs.helper_settings.input_datetime.mode")}:
|
||||
@@ -101,7 +97,6 @@ class HaInputDateTimeForm extends LitElement {
|
||||
value="date"
|
||||
.checked=${this._mode === "date"}
|
||||
@change=${this._modeChanged}
|
||||
.disabled=${this.disabled}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
@@ -114,7 +109,6 @@ class HaInputDateTimeForm extends LitElement {
|
||||
value="time"
|
||||
.checked=${this._mode === "time"}
|
||||
@change=${this._modeChanged}
|
||||
.disabled=${this.disabled}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
@@ -127,7 +121,6 @@ class HaInputDateTimeForm extends LitElement {
|
||||
value="datetime"
|
||||
.checked=${this._mode === "datetime"}
|
||||
@change=${this._modeChanged}
|
||||
.disabled=${this.disabled}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
|
||||
@@ -18,8 +18,6 @@ class HaInputNumberForm extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public new = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
private _item?: Partial<InputNumber>;
|
||||
|
||||
@state() private _name!: string;
|
||||
@@ -91,7 +89,6 @@ class HaInputNumberForm extends LitElement {
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}
|
||||
dialogInitialFocus
|
||||
.disabled=${this.disabled}
|
||||
></ha-textfield>
|
||||
<ha-icon-picker
|
||||
.hass=${this.hass}
|
||||
@@ -101,7 +98,6 @@ class HaInputNumberForm extends LitElement {
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-icon-picker>
|
||||
<ha-textfield
|
||||
.value=${this._min}
|
||||
@@ -112,7 +108,6 @@ class HaInputNumberForm extends LitElement {
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_number.min"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-textfield>
|
||||
<ha-textfield
|
||||
.value=${this._max}
|
||||
@@ -123,7 +118,6 @@ class HaInputNumberForm extends LitElement {
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_number.max"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-textfield>
|
||||
<ha-expansion-panel
|
||||
header=${this.hass.localize(
|
||||
@@ -145,7 +139,6 @@ class HaInputNumberForm extends LitElement {
|
||||
value="slider"
|
||||
.checked=${this._mode === "slider"}
|
||||
@change=${this._modeChanged}
|
||||
.disabled=${this.disabled}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
@@ -158,7 +151,6 @@ class HaInputNumberForm extends LitElement {
|
||||
value="box"
|
||||
.checked=${this._mode === "box"}
|
||||
@change=${this._modeChanged}
|
||||
.disabled=${this.disabled}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
@@ -171,7 +163,6 @@ class HaInputNumberForm extends LitElement {
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_number.step"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-textfield>
|
||||
|
||||
<ha-textfield
|
||||
@@ -181,7 +172,6 @@ class HaInputNumberForm extends LitElement {
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_number.unit_of_measurement"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-textfield>
|
||||
</ha-expansion-panel>
|
||||
</div>
|
||||
|
||||
@@ -23,8 +23,6 @@ class HaInputSelectForm extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public new = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
private _item?: InputSelect;
|
||||
|
||||
@state() private _name!: string;
|
||||
@@ -88,7 +86,6 @@ class HaInputSelectForm extends LitElement {
|
||||
)}
|
||||
.configValue=${"name"}
|
||||
@input=${this._valueChanged}
|
||||
.disabled=${this.disabled}
|
||||
></ha-textfield>
|
||||
<ha-icon-picker
|
||||
.hass=${this.hass}
|
||||
@@ -98,18 +95,13 @@ class HaInputSelectForm extends LitElement {
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-icon-picker>
|
||||
<div class="header">
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_select.options"
|
||||
)}:
|
||||
</div>
|
||||
<ha-sortable
|
||||
@item-moved=${this._optionMoved}
|
||||
handle-selector=".handle"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-sortable @item-moved=${this._optionMoved} handle-selector=".handle">
|
||||
<ha-list class="options">
|
||||
${this._options.length
|
||||
? repeat(
|
||||
@@ -132,7 +124,6 @@ class HaInputSelectForm extends LitElement {
|
||||
"ui.dialogs.helper_settings.input_select.remove_option"
|
||||
)}
|
||||
@click=${this._removeOption}
|
||||
.disabled=${this.disabled}
|
||||
.path=${mdiDelete}
|
||||
></ha-icon-button>
|
||||
</ha-list-item>
|
||||
@@ -155,13 +146,8 @@ class HaInputSelectForm extends LitElement {
|
||||
"ui.dialogs.helper_settings.input_select.add_option"
|
||||
)}
|
||||
@keydown=${this._handleKeyAdd}
|
||||
.disabled=${this.disabled}
|
||||
></ha-textfield>
|
||||
<ha-button
|
||||
size="small"
|
||||
appearance="plain"
|
||||
@click=${this._addOption}
|
||||
.disabled=${this.disabled}
|
||||
<ha-button size="small" appearance="plain" @click=${this._addOption}
|
||||
>${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_select.add"
|
||||
)}</ha-button
|
||||
|
||||
@@ -19,8 +19,6 @@ class HaInputTextForm extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public new = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
private _item?: InputText;
|
||||
|
||||
@state() private _name!: string;
|
||||
@@ -81,7 +79,6 @@ class HaInputTextForm extends LitElement {
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}
|
||||
dialogInitialFocus
|
||||
.disabled=${this.disabled}
|
||||
></ha-textfield>
|
||||
<ha-icon-picker
|
||||
.hass=${this.hass}
|
||||
@@ -91,7 +88,6 @@ class HaInputTextForm extends LitElement {
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-icon-picker>
|
||||
<ha-expansion-panel
|
||||
header=${this.hass.localize(
|
||||
@@ -109,7 +105,6 @@ class HaInputTextForm extends LitElement {
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_text.min"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-textfield>
|
||||
<ha-textfield
|
||||
.value=${this._max}
|
||||
@@ -134,7 +129,6 @@ class HaInputTextForm extends LitElement {
|
||||
value="text"
|
||||
.checked=${this._mode === "text"}
|
||||
@change=${this._modeChanged}
|
||||
.disabled=${this.disabled}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
@@ -147,7 +141,6 @@ class HaInputTextForm extends LitElement {
|
||||
value="password"
|
||||
.checked=${this._mode === "password"}
|
||||
@change=${this._modeChanged}
|
||||
.disabled=${this.disabled}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
@@ -161,7 +154,6 @@ class HaInputTextForm extends LitElement {
|
||||
.helper=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_text.pattern_helper"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-textfield>
|
||||
</ha-expansion-panel>
|
||||
</div>
|
||||
|
||||
@@ -17,9 +17,9 @@ import "../../../../components/ha-textfield";
|
||||
import type { Schedule, ScheduleDay } from "../../../../data/schedule";
|
||||
import { weekdays } from "../../../../data/schedule";
|
||||
import { TimeZone } from "../../../../data/translation";
|
||||
import { showScheduleBlockInfoDialog } from "./show-dialog-schedule-block-info";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { showScheduleBlockInfoDialog } from "./show-dialog-schedule-block-info";
|
||||
|
||||
const defaultFullCalendarConfig: CalendarOptions = {
|
||||
plugins: [timeGridPlugin, interactionPlugin],
|
||||
@@ -43,8 +43,6 @@ class HaScheduleForm extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public new = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@state() private _name!: string;
|
||||
|
||||
@state() private _icon!: string;
|
||||
@@ -134,7 +132,6 @@ class HaScheduleForm extends LitElement {
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}
|
||||
dialogInitialFocus
|
||||
.disabled=${this.disabled}
|
||||
></ha-textfield>
|
||||
<ha-icon-picker
|
||||
.hass=${this.hass}
|
||||
@@ -144,9 +141,8 @@ class HaScheduleForm extends LitElement {
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-icon-picker>
|
||||
${!this.disabled ? html`<div id="calendar"></div>` : nothing}
|
||||
<div id="calendar"></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -179,9 +175,7 @@ class HaScheduleForm extends LitElement {
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
if (!this.disabled) {
|
||||
this._setupCalendar();
|
||||
}
|
||||
this._setupCalendar();
|
||||
}
|
||||
|
||||
private _setupCalendar(): void {
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { createDurationData } from "../../../../common/datetime/create_duration_data";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-checkbox";
|
||||
import "../../../../components/ha-duration-input";
|
||||
import type { HaDurationData } from "../../../../components/ha-duration-input";
|
||||
import "../../../../components/ha-formfield";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
import "../../../../components/ha-duration-input";
|
||||
import "../../../../components/ha-textfield";
|
||||
import type { ForDict } from "../../../../data/automation";
|
||||
import type { DurationDict, Timer } from "../../../../data/timer";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { createDurationData } from "../../../../common/datetime/create_duration_data";
|
||||
import type { HaDurationData } from "../../../../components/ha-duration-input";
|
||||
import type { ForDict } from "../../../../data/automation";
|
||||
|
||||
@customElement("ha-timer-form")
|
||||
class HaTimerForm extends LitElement {
|
||||
@@ -20,8 +20,6 @@ class HaTimerForm extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public new = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
private _item?: Timer;
|
||||
|
||||
@state() private _name!: string;
|
||||
@@ -79,7 +77,6 @@ class HaTimerForm extends LitElement {
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}
|
||||
dialogInitialFocus
|
||||
.disabled=${this.disabled}
|
||||
></ha-textfield>
|
||||
<ha-icon-picker
|
||||
.hass=${this.hass}
|
||||
@@ -89,13 +86,11 @@ class HaTimerForm extends LitElement {
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-icon-picker>
|
||||
<ha-duration-input
|
||||
.configValue=${"duration"}
|
||||
.data=${this._duration_data}
|
||||
@value-changed=${this._valueChanged}
|
||||
.disabled=${this.disabled}
|
||||
></ha-duration-input>
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
@@ -106,7 +101,6 @@ class HaTimerForm extends LitElement {
|
||||
.configValue=${"restore"}
|
||||
.checked=${this._restore}
|
||||
@click=${this._toggleRestore}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
</ha-checkbox>
|
||||
</ha-formfield>
|
||||
@@ -136,9 +130,6 @@ class HaTimerForm extends LitElement {
|
||||
}
|
||||
|
||||
private _toggleRestore() {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
this._restore = !this._restore;
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this._item, restore: this._restore },
|
||||
|
||||
@@ -61,24 +61,6 @@ const processAreasForLight = (
|
||||
return cards;
|
||||
};
|
||||
|
||||
const processUnassignedLights = (
|
||||
hass: HomeAssistant,
|
||||
entities: string[]
|
||||
): LovelaceCardConfig[] => {
|
||||
const unassignedFilter = generateEntityFilter(hass, {
|
||||
area: null,
|
||||
});
|
||||
const unassignedLights = entities.filter(unassignedFilter);
|
||||
const areaCards: LovelaceCardConfig[] = [];
|
||||
const computeTileCard = computeAreaTileCardConfig(hass, "", false);
|
||||
|
||||
for (const entityId of unassignedLights) {
|
||||
areaCards.push(computeTileCard(entityId));
|
||||
}
|
||||
|
||||
return areaCards;
|
||||
};
|
||||
|
||||
@customElement("light-view-strategy")
|
||||
export class LightViewStrategy extends ReactiveElement {
|
||||
static async generate(
|
||||
@@ -154,30 +136,10 @@ export class LightViewStrategy extends ReactiveElement {
|
||||
}
|
||||
}
|
||||
|
||||
// Process unassigned lights
|
||||
const unassignedCards = processUnassignedLights(hass, entities);
|
||||
if (unassignedCards.length > 0) {
|
||||
const section: LovelaceSectionRawConfig = {
|
||||
type: "grid",
|
||||
column_span: 2,
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading:
|
||||
sections.length > 0
|
||||
? hass.localize("ui.panel.lovelace.strategy.light.other_lights")
|
||||
: hass.localize("ui.panel.lovelace.strategy.light.lights"),
|
||||
},
|
||||
...unassignedCards,
|
||||
],
|
||||
};
|
||||
sections.push(section);
|
||||
}
|
||||
|
||||
return {
|
||||
type: "sections",
|
||||
max_columns: 2,
|
||||
sections: sections,
|
||||
sections: sections || [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,15 +419,13 @@ class HuiEnergySankeyCard
|
||||
};
|
||||
deviceNodes.forEach((deviceNode) => {
|
||||
const entity = this.hass.states[deviceNode.id];
|
||||
const { area, floor } = entity
|
||||
? getEntityContext(
|
||||
entity,
|
||||
this.hass.entities,
|
||||
this.hass.devices,
|
||||
this.hass.areas,
|
||||
this.hass.floors
|
||||
)
|
||||
: { area: null, floor: null };
|
||||
const { area, floor } = getEntityContext(
|
||||
entity,
|
||||
this.hass.entities,
|
||||
this.hass.devices,
|
||||
this.hass.areas,
|
||||
this.hass.floors
|
||||
);
|
||||
if (area) {
|
||||
if (area.area_id in areas) {
|
||||
areas[area.area_id].value += deviceNode.value;
|
||||
|
||||
@@ -87,6 +87,11 @@ export class HuiHomeSummaryCard extends LitElement implements LovelaceCard {
|
||||
const allEntities = Object.keys(this.hass!.states);
|
||||
|
||||
const areas = Object.values(this.hass.areas);
|
||||
const areasFilter = generateEntityFilter(this.hass, {
|
||||
area: areas.map((area) => area.area_id),
|
||||
});
|
||||
|
||||
const entitiesInsideArea = allEntities.filter(areasFilter);
|
||||
|
||||
switch (this._config.summary) {
|
||||
case "light": {
|
||||
@@ -95,7 +100,7 @@ export class HuiHomeSummaryCard extends LitElement implements LovelaceCard {
|
||||
generateEntityFilter(this.hass!, filter)
|
||||
);
|
||||
|
||||
const lightEntities = findEntities(allEntities, lightsFilters);
|
||||
const lightEntities = findEntities(entitiesInsideArea, lightsFilters);
|
||||
|
||||
const onLights = lightEntities.filter((entityId) => {
|
||||
const s = this.hass!.states[entityId]?.state;
|
||||
@@ -148,7 +153,7 @@ export class HuiHomeSummaryCard extends LitElement implements LovelaceCard {
|
||||
generateEntityFilter(this.hass!, filter)
|
||||
);
|
||||
|
||||
const safetyEntities = findEntities(allEntities, safetyFilters);
|
||||
const safetyEntities = findEntities(entitiesInsideArea, safetyFilters);
|
||||
|
||||
const locks = safetyEntities.filter((entityId) => {
|
||||
const domain = computeDomain(entityId);
|
||||
@@ -199,7 +204,7 @@ export class HuiHomeSummaryCard extends LitElement implements LovelaceCard {
|
||||
);
|
||||
|
||||
const mediaPlayerEntities = findEntities(
|
||||
allEntities,
|
||||
entitiesInsideArea,
|
||||
mediaPlayerFilters
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
mdiAccount,
|
||||
mdiAmpersand,
|
||||
mdiCalendarClock,
|
||||
mdiGateOr,
|
||||
mdiMapMarker,
|
||||
mdiNotEqualVariant,
|
||||
@@ -16,7 +15,6 @@ export const ICON_CONDITION: Record<Condition["condition"], string> = {
|
||||
numeric_state: mdiNumeric,
|
||||
state: mdiStateMachine,
|
||||
screen: mdiResponsive,
|
||||
time: mdiCalendarClock,
|
||||
user: mdiAccount,
|
||||
and: mdiAmpersand,
|
||||
not: mdiNotEqualVariant,
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import { ensureArray } from "../../../common/array/ensure-array";
|
||||
import { checkTimeInRange } from "../../../common/datetime/check_time";
|
||||
import {
|
||||
WEEKDAYS_SHORT,
|
||||
type WeekdayShort,
|
||||
} from "../../../common/datetime/weekday";
|
||||
import type { MediaQueriesListener } from "../../../common/dom/media_query";
|
||||
import { listenMediaQuery } from "../../../common/dom/media_query";
|
||||
|
||||
@@ -17,7 +12,6 @@ export type Condition =
|
||||
| NumericStateCondition
|
||||
| StateCondition
|
||||
| ScreenCondition
|
||||
| TimeCondition
|
||||
| UserCondition
|
||||
| OrCondition
|
||||
| AndCondition
|
||||
@@ -58,13 +52,6 @@ export interface ScreenCondition extends BaseCondition {
|
||||
media_query?: string;
|
||||
}
|
||||
|
||||
export interface TimeCondition extends BaseCondition {
|
||||
condition: "time";
|
||||
after?: string;
|
||||
before?: string;
|
||||
weekdays?: WeekdayShort[];
|
||||
}
|
||||
|
||||
export interface UserCondition extends BaseCondition {
|
||||
condition: "user";
|
||||
users?: string[];
|
||||
@@ -165,15 +152,6 @@ function checkScreenCondition(condition: ScreenCondition, _: HomeAssistant) {
|
||||
: false;
|
||||
}
|
||||
|
||||
function checkTimeCondition(condition: TimeCondition, hass: HomeAssistant) {
|
||||
return checkTimeInRange(
|
||||
hass,
|
||||
condition.after,
|
||||
condition.before,
|
||||
condition.weekdays
|
||||
);
|
||||
}
|
||||
|
||||
function checkLocationCondition(
|
||||
condition: LocationCondition,
|
||||
hass: HomeAssistant
|
||||
@@ -219,8 +197,6 @@ export function checkConditionsMet(
|
||||
return conditions.every((c) => {
|
||||
if ("condition" in c) {
|
||||
switch (c.condition) {
|
||||
case "time":
|
||||
return checkTimeCondition(c, hass);
|
||||
case "screen":
|
||||
return checkScreenCondition(c, hass);
|
||||
case "user":
|
||||
@@ -297,17 +273,6 @@ function validateScreenCondition(condition: ScreenCondition) {
|
||||
return condition.media_query != null;
|
||||
}
|
||||
|
||||
function validateTimeCondition(condition: TimeCondition) {
|
||||
const hasTime = condition.after != null || condition.before != null;
|
||||
const hasWeekdays =
|
||||
condition.weekdays != null && condition.weekdays.length > 0;
|
||||
const weekdaysValid =
|
||||
!hasWeekdays ||
|
||||
condition.weekdays!.every((w: WeekdayShort) => WEEKDAYS_SHORT.includes(w));
|
||||
|
||||
return (hasTime || hasWeekdays) && weekdaysValid;
|
||||
}
|
||||
|
||||
function validateUserCondition(condition: UserCondition) {
|
||||
return condition.users != null;
|
||||
}
|
||||
@@ -347,8 +312,6 @@ export function validateConditionalConfig(
|
||||
switch (c.condition) {
|
||||
case "screen":
|
||||
return validateScreenCondition(c);
|
||||
case "time":
|
||||
return validateTimeCondition(c);
|
||||
case "user":
|
||||
return validateUserCondition(c);
|
||||
case "location":
|
||||
|
||||
@@ -25,7 +25,6 @@ import "./types/ha-card-condition-numeric_state";
|
||||
import "./types/ha-card-condition-or";
|
||||
import "./types/ha-card-condition-screen";
|
||||
import "./types/ha-card-condition-state";
|
||||
import "./types/ha-card-condition-time";
|
||||
import "./types/ha-card-condition-user";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
|
||||
@@ -34,7 +33,6 @@ const UI_CONDITION = [
|
||||
"numeric_state",
|
||||
"state",
|
||||
"screen",
|
||||
"time",
|
||||
"user",
|
||||
"and",
|
||||
"not",
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import {
|
||||
literal,
|
||||
array,
|
||||
object,
|
||||
optional,
|
||||
string,
|
||||
assert,
|
||||
enums,
|
||||
} from "superstruct";
|
||||
import memoizeOne from "memoize-one";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
||||
import {
|
||||
WEEKDAY_SHORT_TO_LONG,
|
||||
WEEKDAYS_SHORT,
|
||||
} from "../../../../../common/datetime/weekday";
|
||||
import type { TimeCondition } from "../../../common/validate-condition";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import type {
|
||||
HaFormSchema,
|
||||
SchemaUnion,
|
||||
} from "../../../../../components/ha-form/types";
|
||||
import "../../../../../components/ha-form/ha-form";
|
||||
|
||||
const timeConditionStruct = object({
|
||||
condition: literal("time"),
|
||||
after: optional(string()),
|
||||
before: optional(string()),
|
||||
weekdays: optional(array(enums(WEEKDAYS_SHORT))),
|
||||
});
|
||||
|
||||
@customElement("ha-card-condition-time")
|
||||
export class HaCardConditionTime extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public condition!: TimeCondition;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
public static get defaultConfig(): TimeCondition {
|
||||
return { condition: "time", after: "08:00", before: "17:00" };
|
||||
}
|
||||
|
||||
protected static validateUIConfig(condition: TimeCondition) {
|
||||
return assert(condition, timeConditionStruct);
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(localize: LocalizeFunc) =>
|
||||
[
|
||||
{ name: "after", selector: { time: {} } },
|
||||
{ name: "before", selector: { time: {} } },
|
||||
{
|
||||
name: "weekdays",
|
||||
selector: {
|
||||
select: {
|
||||
mode: "list",
|
||||
multiple: true,
|
||||
options: WEEKDAYS_SHORT.map((day) => ({
|
||||
value: day,
|
||||
label: localize(`ui.weekdays.${WEEKDAY_SHORT_TO_LONG[day]}`),
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
] as const satisfies HaFormSchema[]
|
||||
);
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this.condition}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
.schema=${this._schema(this.hass.localize)}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const data = ev.detail.value as TimeCondition;
|
||||
fireEvent(this, "value-changed", { value: data });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
): string =>
|
||||
this.hass.localize(
|
||||
`ui.panel.lovelace.editor.condition-editor.condition.time.${schema.name}`
|
||||
);
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-card-condition-time": HaCardConditionTime;
|
||||
}
|
||||
}
|
||||
@@ -144,80 +144,67 @@ export class HomeMainViewStrategy extends ReactiveElement {
|
||||
column_span: maxColumns,
|
||||
} as LovelaceStrategySectionConfig;
|
||||
|
||||
// Check if there are any media player entities
|
||||
const mediaPlayerFilter = generateEntityFilter(hass, {
|
||||
domain: "media_player",
|
||||
entity_category: "none",
|
||||
});
|
||||
const hasMediaPlayers = Object.keys(hass.states).some(mediaPlayerFilter);
|
||||
|
||||
const summaryCards: LovelaceCardConfig[] = [
|
||||
{
|
||||
type: "heading",
|
||||
heading: hass.localize("ui.panel.lovelace.strategy.home.summaries"),
|
||||
},
|
||||
{
|
||||
type: "home-summary",
|
||||
summary: "light",
|
||||
vertical: true,
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: "/light?historyBack=1",
|
||||
},
|
||||
grid_options: {
|
||||
rows: 2,
|
||||
columns: 4,
|
||||
},
|
||||
} satisfies HomeSummaryCard,
|
||||
{
|
||||
type: "home-summary",
|
||||
summary: "climate",
|
||||
vertical: true,
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: "/climate?historyBack=1",
|
||||
},
|
||||
grid_options: {
|
||||
rows: 2,
|
||||
columns: 4,
|
||||
},
|
||||
} satisfies HomeSummaryCard,
|
||||
{
|
||||
type: "home-summary",
|
||||
summary: "safety",
|
||||
vertical: true,
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: "/safety?historyBack=1",
|
||||
},
|
||||
grid_options: {
|
||||
rows: 2,
|
||||
columns: 4,
|
||||
},
|
||||
} satisfies HomeSummaryCard,
|
||||
];
|
||||
|
||||
// Only add media players summary if there are media player entities
|
||||
if (hasMediaPlayers) {
|
||||
summaryCards.push({
|
||||
type: "home-summary",
|
||||
summary: "media_players",
|
||||
vertical: true,
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: "media-players",
|
||||
},
|
||||
grid_options: {
|
||||
rows: 2,
|
||||
columns: 4,
|
||||
},
|
||||
} satisfies HomeSummaryCard);
|
||||
}
|
||||
|
||||
const summarySection: LovelaceSectionConfig = {
|
||||
type: "grid",
|
||||
column_span: maxColumns,
|
||||
cards: summaryCards,
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading: hass.localize("ui.panel.lovelace.strategy.home.summaries"),
|
||||
},
|
||||
{
|
||||
type: "home-summary",
|
||||
summary: "light",
|
||||
vertical: true,
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: "/light?historyBack=1",
|
||||
},
|
||||
grid_options: {
|
||||
rows: 2,
|
||||
columns: 4,
|
||||
},
|
||||
} satisfies HomeSummaryCard,
|
||||
{
|
||||
type: "home-summary",
|
||||
summary: "climate",
|
||||
vertical: true,
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: "/climate?historyBack=1",
|
||||
},
|
||||
grid_options: {
|
||||
rows: 2,
|
||||
columns: 4,
|
||||
},
|
||||
} satisfies HomeSummaryCard,
|
||||
{
|
||||
type: "home-summary",
|
||||
summary: "safety",
|
||||
vertical: true,
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: "/safety?historyBack=1",
|
||||
},
|
||||
grid_options: {
|
||||
rows: 2,
|
||||
columns: 4,
|
||||
},
|
||||
} satisfies HomeSummaryCard,
|
||||
{
|
||||
type: "home-summary",
|
||||
summary: "media_players",
|
||||
vertical: true,
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: "media-players",
|
||||
},
|
||||
grid_options: {
|
||||
rows: 2,
|
||||
columns: 4,
|
||||
},
|
||||
} satisfies HomeSummaryCard,
|
||||
],
|
||||
};
|
||||
|
||||
const weatherFilter = generateEntityFilter(hass, {
|
||||
|
||||
@@ -59,26 +59,6 @@ const processAreasForMediaPlayers = (
|
||||
return cards;
|
||||
};
|
||||
|
||||
const processUnassignedEntities = (
|
||||
hass: HomeAssistant,
|
||||
entities: string[]
|
||||
): LovelaceCardConfig[] => {
|
||||
const unassignedFilter = generateEntityFilter(hass, {
|
||||
area: null,
|
||||
});
|
||||
const unassignedEntities = entities.filter(unassignedFilter);
|
||||
const areaCards: LovelaceCardConfig[] = [];
|
||||
|
||||
for (const entityId of unassignedEntities) {
|
||||
areaCards.push({
|
||||
type: "media-control",
|
||||
entity: entityId,
|
||||
} satisfies MediaControlCardConfig);
|
||||
}
|
||||
|
||||
return areaCards;
|
||||
};
|
||||
|
||||
@customElement("home-media-players-view-strategy")
|
||||
export class HomeMMediaPlayersViewStrategy extends ReactiveElement {
|
||||
static async generate(
|
||||
@@ -154,35 +134,10 @@ export class HomeMMediaPlayersViewStrategy extends ReactiveElement {
|
||||
}
|
||||
}
|
||||
|
||||
// Process unassigned entities
|
||||
const unassignedCards = processUnassignedEntities(hass, entities);
|
||||
|
||||
if (unassignedCards.length > 0) {
|
||||
const section: LovelaceSectionRawConfig = {
|
||||
type: "grid",
|
||||
column_span: 2,
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading:
|
||||
sections.length > 0
|
||||
? hass.localize(
|
||||
"ui.panel.lovelace.strategy.home_media_players.other_media_players"
|
||||
)
|
||||
: hass.localize(
|
||||
"ui.panel.lovelace.strategy.home_media_players.media_players"
|
||||
),
|
||||
},
|
||||
...unassignedCards,
|
||||
],
|
||||
};
|
||||
sections.push(section);
|
||||
}
|
||||
|
||||
return {
|
||||
type: "sections",
|
||||
max_columns: 2,
|
||||
sections: sections,
|
||||
sections: sections || [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ import {
|
||||
} from "../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../../components/ha-dropdown-item";
|
||||
import "../../components/ha-dropdown";
|
||||
|
||||
// Client ID used by iOS app
|
||||
const iOSclientId = "https://home-assistant.io/iOS";
|
||||
@@ -146,19 +148,18 @@ class HaRefreshTokens extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<ha-md-button-menu positioning="popover">
|
||||
<ha-dropdown>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<ha-md-menu-item
|
||||
graphic="icon"
|
||||
<ha-dropdown-item
|
||||
@click=${this._toggleTokenExpiration}
|
||||
.token=${token}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${token.expire_at
|
||||
? mdiClockRemoveOutline
|
||||
: mdiClockCheckOutline}
|
||||
@@ -170,24 +171,20 @@ class HaRefreshTokens extends LitElement {
|
||||
: this.hass.localize(
|
||||
"ui.panel.profile.refresh_tokens.enable_token_expiration"
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
graphic="icon"
|
||||
class="warning"
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
variant="danger"
|
||||
.disabled=${token.is_current}
|
||||
@click=${this._deleteToken}
|
||||
.token=${token}
|
||||
>
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
${this.hass.localize("ui.common.delete")}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
${this.hass.localize("ui.common.delete")}
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
</div>
|
||||
</ha-settings-row>
|
||||
`
|
||||
|
||||
@@ -103,24 +103,6 @@ const processAreasForSafety = (
|
||||
return cards;
|
||||
};
|
||||
|
||||
const processUnassignedEntities = (
|
||||
hass: HomeAssistant,
|
||||
entities: string[]
|
||||
): LovelaceCardConfig[] => {
|
||||
const unassignedFilter = generateEntityFilter(hass, {
|
||||
area: null,
|
||||
});
|
||||
const unassignedLights = entities.filter(unassignedFilter);
|
||||
const areaCards: LovelaceCardConfig[] = [];
|
||||
const computeTileCard = computeAreaTileCardConfig(hass, "", false);
|
||||
|
||||
for (const entityId of unassignedLights) {
|
||||
areaCards.push(computeTileCard(entityId));
|
||||
}
|
||||
|
||||
return areaCards;
|
||||
};
|
||||
|
||||
@customElement("safety-view-strategy")
|
||||
export class SafetyViewStrategy extends ReactiveElement {
|
||||
static async generate(
|
||||
@@ -196,33 +178,10 @@ export class SafetyViewStrategy extends ReactiveElement {
|
||||
}
|
||||
}
|
||||
|
||||
// Process unassigned entities
|
||||
const unassignedCards = processUnassignedEntities(hass, entities);
|
||||
|
||||
if (unassignedCards.length > 0) {
|
||||
const section: LovelaceSectionRawConfig = {
|
||||
type: "grid",
|
||||
column_span: 2,
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading:
|
||||
sections.length > 0
|
||||
? hass.localize(
|
||||
"ui.panel.lovelace.strategy.safety.other_devices"
|
||||
)
|
||||
: hass.localize("ui.panel.lovelace.strategy.safety.devices"),
|
||||
},
|
||||
...unassignedCards,
|
||||
],
|
||||
};
|
||||
sections.push(section);
|
||||
}
|
||||
|
||||
return {
|
||||
type: "sections",
|
||||
max_columns: 2,
|
||||
sections: sections,
|
||||
sections: sections || [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,6 +155,10 @@ export const semanticColorStyles = css`
|
||||
|
||||
/* Surfaces */
|
||||
--ha-color-surface-default: var(--ha-color-neutral-95);
|
||||
--ha-color-on-surface-default: var(--ha-color-neutral-05);
|
||||
|
||||
/* shadow */
|
||||
--ha-color-shadow: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -286,5 +290,9 @@ export const darkSemanticColorStyles = css`
|
||||
|
||||
/* Surfaces */
|
||||
--ha-color-surface-default: var(--ha-color-neutral-10);
|
||||
--ha-color-on-surface-default: var(--ha-color-neutral-95);
|
||||
|
||||
/* shadow */
|
||||
--ha-color-shadow: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -52,7 +52,9 @@ export const waColorStyles = css`
|
||||
--wa-color-danger-on-normal: var(--ha-color-on-danger-normal);
|
||||
--wa-color-danger-on-quiet: var(--ha-color-on-danger-quiet);
|
||||
|
||||
--wa-color-surface-default: var(--card-background-color);
|
||||
--wa-color-text-normal: var(--ha-color-text-primary);
|
||||
--wa-color-surface-default: var(--card-background-color);
|
||||
--wa-color-surface-raised: var(--ha-dialog-surface-background, var(--mdc-theme-surface, #fff));
|
||||
--wa-panel-border-radius: var(--ha-border-radius-3xl);
|
||||
--wa-panel-border-style: solid;
|
||||
--wa-panel-border-width: 1px;
|
||||
|
||||
@@ -16,7 +16,17 @@ export const waMainStyles = css`
|
||||
--wa-font-weight-action: var(--ha-font-weight-medium);
|
||||
--wa-transition-fast: 75ms;
|
||||
--wa-transition-easing: ease;
|
||||
--wa-border-width-l: var(--ha-border-radius-lg);
|
||||
|
||||
--wa-border-style: solid;
|
||||
--wa-border-width-s: var(--ha-border-width-sm);
|
||||
--wa-border-width-m: var(--ha-border-width-md);
|
||||
--wa-border-width-l: var(--ha-border-width-lg);
|
||||
--wa-border-radius-s: var(--ha-border-radius-sm);
|
||||
--wa-border-radius-m: var(--ha-border-radius-md);
|
||||
--wa-border-radius-l: var(--ha-border-radius-lg);
|
||||
|
||||
--wa-line-height-condensed: 1.25;
|
||||
|
||||
--wa-space-xl: 32px;
|
||||
}
|
||||
|
||||
|
||||
@@ -6975,22 +6975,6 @@
|
||||
"common_controls": {
|
||||
"not_loaded": "Usage Prediction integration is not loaded.",
|
||||
"no_data": "This place will soon fill up with the entities you use most often, based on your activity."
|
||||
},
|
||||
"light": {
|
||||
"lights": "Lights",
|
||||
"other_lights": "Other lights"
|
||||
},
|
||||
"safety": {
|
||||
"devices": "Devices",
|
||||
"other_devices": "Other devices"
|
||||
},
|
||||
"climate": {
|
||||
"devices": "Devices",
|
||||
"other_devices": "Other devices"
|
||||
},
|
||||
"home_media_players": {
|
||||
"media_players": "Media players",
|
||||
"other_media_players": "Other media players"
|
||||
}
|
||||
},
|
||||
"cards": {
|
||||
@@ -7549,12 +7533,6 @@
|
||||
"state_equal": "State is equal to",
|
||||
"state_not_equal": "State is not equal to"
|
||||
},
|
||||
"time": {
|
||||
"label": "Time",
|
||||
"after": "After",
|
||||
"before": "Before",
|
||||
"weekdays": "Weekdays"
|
||||
},
|
||||
"location": {
|
||||
"label": "Location",
|
||||
"locations": "Locations",
|
||||
|
||||
@@ -1,209 +0,0 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { checkTimeInRange } from "../../../src/common/datetime/check_time";
|
||||
import type { HomeAssistant } from "../../../src/types";
|
||||
import {
|
||||
NumberFormat,
|
||||
TimeFormat,
|
||||
FirstWeekday,
|
||||
DateFormat,
|
||||
TimeZone,
|
||||
} from "../../../src/data/translation";
|
||||
|
||||
describe("checkTimeInRange", () => {
|
||||
let mockHass: HomeAssistant;
|
||||
|
||||
beforeEach(() => {
|
||||
mockHass = {
|
||||
locale: {
|
||||
language: "en-US",
|
||||
number_format: NumberFormat.language,
|
||||
time_format: TimeFormat.language,
|
||||
date_format: DateFormat.language,
|
||||
time_zone: TimeZone.local,
|
||||
first_weekday: FirstWeekday.language,
|
||||
},
|
||||
config: {
|
||||
time_zone: "America/Los_Angeles",
|
||||
},
|
||||
} as HomeAssistant;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
describe("time ranges within same day", () => {
|
||||
it("should return true when current time is within range", () => {
|
||||
// Set time to 10:00 AM
|
||||
vi.setSystemTime(new Date(2024, 0, 15, 10, 0, 0));
|
||||
|
||||
expect(checkTimeInRange(mockHass, "08:00", "17:00")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when current time is before range", () => {
|
||||
// Set time to 7:00 AM
|
||||
vi.setSystemTime(new Date(2024, 0, 15, 7, 0, 0));
|
||||
|
||||
expect(checkTimeInRange(mockHass, "08:00", "17:00")).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when current time is after range", () => {
|
||||
// Set time to 6:00 PM
|
||||
vi.setSystemTime(new Date(2024, 0, 15, 18, 0, 0));
|
||||
|
||||
expect(checkTimeInRange(mockHass, "08:00", "17:00")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("time ranges crossing midnight", () => {
|
||||
it("should return true when current time is before midnight", () => {
|
||||
// Set time to 11:00 PM
|
||||
vi.setSystemTime(new Date(2024, 0, 15, 23, 0, 0));
|
||||
|
||||
expect(checkTimeInRange(mockHass, "22:00", "06:00")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true when current time is after midnight", () => {
|
||||
// Set time to 3:00 AM
|
||||
vi.setSystemTime(new Date(2024, 0, 15, 3, 0, 0));
|
||||
|
||||
expect(checkTimeInRange(mockHass, "22:00", "06:00")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when outside the range", () => {
|
||||
// Set time to 10:00 AM
|
||||
vi.setSystemTime(new Date(2024, 0, 15, 10, 0, 0));
|
||||
|
||||
expect(checkTimeInRange(mockHass, "22:00", "06:00")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("only 'after' condition", () => {
|
||||
it("should return true when after specified time", () => {
|
||||
vi.setSystemTime(new Date(2024, 0, 15, 10, 0, 0));
|
||||
expect(checkTimeInRange(mockHass, "08:00")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when before specified time", () => {
|
||||
vi.setSystemTime(new Date(2024, 0, 15, 6, 0, 0));
|
||||
expect(checkTimeInRange(mockHass, "08:00")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("only 'before' condition", () => {
|
||||
it("should return true when before specified time", () => {
|
||||
vi.setSystemTime(new Date(2024, 0, 15, 10, 0, 0));
|
||||
expect(checkTimeInRange(mockHass, undefined, "17:00")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when after specified time", () => {
|
||||
vi.setSystemTime(new Date(2024, 0, 15, 18, 0, 0));
|
||||
expect(checkTimeInRange(mockHass, undefined, "17:00")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("weekday filtering", () => {
|
||||
it("should return true on matching weekday", () => {
|
||||
// January 15, 2024 is a Monday
|
||||
vi.setSystemTime(new Date(2024, 0, 15, 10, 0, 0));
|
||||
|
||||
expect(checkTimeInRange(mockHass, undefined, undefined, ["mon"])).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it("should return false on non-matching weekday", () => {
|
||||
// January 15, 2024 is a Monday
|
||||
vi.setSystemTime(new Date(2024, 0, 15, 10, 0, 0));
|
||||
|
||||
expect(checkTimeInRange(mockHass, undefined, undefined, ["tue"])).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it("should work with multiple weekdays", () => {
|
||||
// January 15, 2024 is a Monday
|
||||
vi.setSystemTime(new Date(2024, 0, 15, 10, 0, 0));
|
||||
|
||||
expect(
|
||||
checkTimeInRange(mockHass, undefined, undefined, ["mon", "wed", "fri"])
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("combined time and weekday conditions", () => {
|
||||
it("should return true when both match", () => {
|
||||
// January 15, 2024 is a Monday at 10:00 AM
|
||||
vi.setSystemTime(new Date(2024, 0, 15, 10, 0, 0));
|
||||
|
||||
expect(checkTimeInRange(mockHass, "08:00", "17:00", ["mon"])).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when time matches but weekday doesn't", () => {
|
||||
// January 15, 2024 is a Monday at 10:00 AM
|
||||
vi.setSystemTime(new Date(2024, 0, 15, 10, 0, 0));
|
||||
|
||||
expect(checkTimeInRange(mockHass, "08:00", "17:00", ["tue"])).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when weekday matches but time doesn't", () => {
|
||||
// January 15, 2024 is a Monday at 6:00 AM
|
||||
vi.setSystemTime(new Date(2024, 0, 15, 6, 0, 0));
|
||||
|
||||
expect(checkTimeInRange(mockHass, "08:00", "17:00", ["mon"])).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("no conditions", () => {
|
||||
it("should return true when no conditions specified", () => {
|
||||
vi.setSystemTime(new Date(2024, 0, 15, 10, 0, 0));
|
||||
|
||||
expect(checkTimeInRange(mockHass)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("DST transitions", () => {
|
||||
it("should handle spring forward transition (losing an hour)", () => {
|
||||
// March 10, 2024 at 1:30 AM PST - before spring forward
|
||||
// At 2:00 AM, clocks jump to 3:00 AM PDT
|
||||
vi.setSystemTime(new Date(2024, 2, 10, 1, 30, 0));
|
||||
|
||||
// Should be within range that crosses the transition
|
||||
expect(checkTimeInRange(mockHass, "01:00", "04:00")).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle spring forward transition after the jump", () => {
|
||||
// March 10, 2024 at 3:30 AM PDT - after spring forward
|
||||
vi.setSystemTime(new Date(2024, 2, 10, 3, 30, 0));
|
||||
|
||||
// Should still be within range
|
||||
expect(checkTimeInRange(mockHass, "01:00", "04:00")).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle fall back transition (gaining an hour)", () => {
|
||||
// November 3, 2024 at 1:30 AM PDT - before fall back
|
||||
// At 2:00 AM PDT, clocks fall back to 1:00 AM PST
|
||||
vi.setSystemTime(new Date(2024, 10, 3, 1, 30, 0));
|
||||
|
||||
// Should be within range that crosses the transition
|
||||
expect(checkTimeInRange(mockHass, "01:00", "03:00")).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle midnight crossing during DST transition", () => {
|
||||
// March 10, 2024 at 1:00 AM - during spring forward night
|
||||
vi.setSystemTime(new Date(2024, 2, 10, 1, 0, 0));
|
||||
|
||||
// Range that crosses midnight and DST transition
|
||||
expect(checkTimeInRange(mockHass, "22:00", "04:00")).toBe(true);
|
||||
});
|
||||
|
||||
it("should correctly compare times on DST transition day", () => {
|
||||
// November 3, 2024 at 10:00 AM - after fall back completed
|
||||
vi.setSystemTime(new Date(2024, 10, 3, 10, 0, 0));
|
||||
|
||||
// Normal business hours should work correctly
|
||||
expect(checkTimeInRange(mockHass, "08:00", "17:00")).toBe(true);
|
||||
expect(checkTimeInRange(mockHass, "12:00", "17:00")).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -388,70 +388,4 @@ describe("generateEntityFilter", () => {
|
||||
expect(filter("light.no_area")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("null filtering", () => {
|
||||
it("should filter entities with no area when null is used", () => {
|
||||
const filter = generateEntityFilter(mockHass, { area: null });
|
||||
|
||||
expect(filter("light.no_area")).toBe(true);
|
||||
expect(filter("light.living_room")).toBe(false);
|
||||
});
|
||||
|
||||
it("should filter entities with specific area OR no area when null is in array", () => {
|
||||
const filter = generateEntityFilter(mockHass, {
|
||||
area: ["living_room", null],
|
||||
});
|
||||
|
||||
expect(filter("light.living_room")).toBe(true);
|
||||
expect(filter("sensor.temperature")).toBe(true);
|
||||
expect(filter("light.no_area")).toBe(true);
|
||||
expect(filter("switch.kitchen")).toBe(false);
|
||||
});
|
||||
|
||||
it("should filter entities with no floor when null is used", () => {
|
||||
const filter = generateEntityFilter(mockHass, { floor: null });
|
||||
|
||||
expect(filter("light.no_area")).toBe(true);
|
||||
expect(filter("light.living_room")).toBe(false);
|
||||
});
|
||||
|
||||
it("should filter entities with specific floor OR no floor", () => {
|
||||
const filter = generateEntityFilter(mockHass, {
|
||||
floor: ["main_floor", null],
|
||||
});
|
||||
|
||||
expect(filter("light.living_room")).toBe(true);
|
||||
expect(filter("switch.kitchen")).toBe(true);
|
||||
expect(filter("light.no_area")).toBe(true);
|
||||
expect(filter("light.bedroom")).toBe(false);
|
||||
});
|
||||
|
||||
it("should filter entities with no device when null is used", () => {
|
||||
const filter = generateEntityFilter(mockHass, { device: null });
|
||||
|
||||
expect(filter("light.living_room")).toBe(false);
|
||||
expect(filter("light.no_area")).toBe(false);
|
||||
});
|
||||
|
||||
it("should filter entities with specific device OR no device", () => {
|
||||
const filter = generateEntityFilter(mockHass, {
|
||||
device: ["device1", null],
|
||||
});
|
||||
|
||||
expect(filter("light.living_room")).toBe(true);
|
||||
expect(filter("switch.kitchen")).toBe(false);
|
||||
});
|
||||
|
||||
it("should combine null filtering with other criteria", () => {
|
||||
const filter = generateEntityFilter(mockHass, {
|
||||
domain: "light",
|
||||
area: ["living_room", null],
|
||||
});
|
||||
|
||||
expect(filter("light.living_room")).toBe(true);
|
||||
expect(filter("light.no_area")).toBe(true);
|
||||
expect(filter("light.bedroom")).toBe(false);
|
||||
expect(filter("sensor.temperature")).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
118
yarn.lock
118
yarn.lock
@@ -3446,18 +3446,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/core@npm:^7.0.6":
|
||||
version: 7.0.6
|
||||
resolution: "@octokit/core@npm:7.0.6"
|
||||
"@octokit/core@npm:^7.0.2":
|
||||
version: 7.0.4
|
||||
resolution: "@octokit/core@npm:7.0.4"
|
||||
dependencies:
|
||||
"@octokit/auth-token": "npm:^6.0.0"
|
||||
"@octokit/graphql": "npm:^9.0.3"
|
||||
"@octokit/request": "npm:^10.0.6"
|
||||
"@octokit/request-error": "npm:^7.0.2"
|
||||
"@octokit/types": "npm:^16.0.0"
|
||||
"@octokit/graphql": "npm:^9.0.1"
|
||||
"@octokit/request": "npm:^10.0.2"
|
||||
"@octokit/request-error": "npm:^7.0.0"
|
||||
"@octokit/types": "npm:^15.0.0"
|
||||
before-after-hook: "npm:^4.0.0"
|
||||
universal-user-agent: "npm:^7.0.0"
|
||||
checksum: 10/852d41fc3150d2a891156427dd0575c77889f1c7a109894ee541594e3fd47c0d4e0a93fee22966c507dfd6158b522e42846c2ac46b9d896078194c95fa81f4ae
|
||||
checksum: 10/d691df211ba9a2941ec97dc32e1c34e26e7c8161425fb573425cfc2727f548bf2a2445eec9d36748b4b469478feb21de4bec1c00a34aa02eedf5b7625ca52189
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3471,14 +3471,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/graphql@npm:^9.0.3":
|
||||
version: 9.0.3
|
||||
resolution: "@octokit/graphql@npm:9.0.3"
|
||||
"@octokit/graphql@npm:^9.0.1":
|
||||
version: 9.0.1
|
||||
resolution: "@octokit/graphql@npm:9.0.1"
|
||||
dependencies:
|
||||
"@octokit/request": "npm:^10.0.6"
|
||||
"@octokit/types": "npm:^16.0.0"
|
||||
"@octokit/request": "npm:^10.0.2"
|
||||
"@octokit/types": "npm:^14.0.0"
|
||||
universal-user-agent: "npm:^7.0.0"
|
||||
checksum: 10/7b16f281f8571dce55280b3986fbb8d15465a7236164a5f6497ded7597ff9ee95d5796924555b979903fe8c6706fe6be1b3e140d807297f85ac8edeadc28f9fe
|
||||
checksum: 10/02d7ea4e2c17a4d4b7311150d0326318c756aff6cf955d9ba443a4bf26b32784832060379fc74f4537657415b262c10adb7f4a1655e15b143d19c2f099b87f16
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3501,6 +3501,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/openapi-types@npm:^25.1.0":
|
||||
version: 25.1.0
|
||||
resolution: "@octokit/openapi-types@npm:25.1.0"
|
||||
checksum: 10/91989a4cec12250e6b3226e9aa931c05c27d46a946725d01e6a831af3890f157210a7032f07641a156c608cc6bf6cf55a28f07179910b644966358d6d559dec6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/openapi-types@npm:^26.0.0":
|
||||
version: 26.0.0
|
||||
resolution: "@octokit/openapi-types@npm:26.0.0"
|
||||
checksum: 10/b9e1b1230b0a3d280b48902a927ce4e7df0d51096c928e2ee929035b0bce779fe7748a1ae58696f1c3080bf8338b6388d5caba5b0dbf254e9713303ed3abf7c2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/openapi-types@npm:^27.0.0":
|
||||
version: 27.0.0
|
||||
resolution: "@octokit/openapi-types@npm:27.0.0"
|
||||
@@ -3508,14 +3522,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/plugin-paginate-rest@npm:^14.0.0":
|
||||
version: 14.0.0
|
||||
resolution: "@octokit/plugin-paginate-rest@npm:14.0.0"
|
||||
"@octokit/plugin-paginate-rest@npm:^13.0.1":
|
||||
version: 13.1.1
|
||||
resolution: "@octokit/plugin-paginate-rest@npm:13.1.1"
|
||||
dependencies:
|
||||
"@octokit/types": "npm:^16.0.0"
|
||||
"@octokit/types": "npm:^14.1.0"
|
||||
peerDependencies:
|
||||
"@octokit/core": ">=6"
|
||||
checksum: 10/57ddd857528dad9c02431bc6254c2374c06057872cf9656a4a88b162ebe1c2bc9f34fbec360f2ccff72c940f29b120758ce14e8135bd027223d381eb1b8b6579
|
||||
checksum: 10/26b9b7a233b77fff31d31469879a281e651417df86799387d6563446f037c9969061b833ab0698857c9797ea856f2ef7ed6970d8fb471239879c9298bbabe200
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3528,14 +3542,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/plugin-rest-endpoint-methods@npm:^17.0.0":
|
||||
version: 17.0.0
|
||||
resolution: "@octokit/plugin-rest-endpoint-methods@npm:17.0.0"
|
||||
"@octokit/plugin-rest-endpoint-methods@npm:^16.0.0":
|
||||
version: 16.1.0
|
||||
resolution: "@octokit/plugin-rest-endpoint-methods@npm:16.1.0"
|
||||
dependencies:
|
||||
"@octokit/types": "npm:^16.0.0"
|
||||
"@octokit/types": "npm:^15.0.0"
|
||||
peerDependencies:
|
||||
"@octokit/core": ">=6"
|
||||
checksum: 10/e9d9ad4d9755cc7fb82fdcbfa870ddea8a432180f0f76c8469095557fd1e26f8caea8cae58401209be17c4f3d8cc48c0e16a3643e37e48f4d23c39e058bf2c55
|
||||
checksum: 10/9b62d1ddf3435d77cbf11f36abf951b755cf2033dfd5f1875d35f97ad4ab9391e7d0e008ae5b813b7fe0b6c537ab38240b4911c61b53cf8fb8d9957a3d898e09
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3552,7 +3566,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/request-error@npm:^7.0.2":
|
||||
"@octokit/request-error@npm:^7.0.0, @octokit/request-error@npm:^7.0.2":
|
||||
version: 7.0.2
|
||||
resolution: "@octokit/request-error@npm:7.0.2"
|
||||
dependencies:
|
||||
@@ -3561,7 +3575,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/request@npm:^10.0.6":
|
||||
"@octokit/request@npm:^10.0.2, @octokit/request@npm:^10.0.6":
|
||||
version: 10.0.6
|
||||
resolution: "@octokit/request@npm:10.0.6"
|
||||
dependencies:
|
||||
@@ -3574,15 +3588,33 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/rest@npm:22.0.1":
|
||||
version: 22.0.1
|
||||
resolution: "@octokit/rest@npm:22.0.1"
|
||||
"@octokit/rest@npm:22.0.0":
|
||||
version: 22.0.0
|
||||
resolution: "@octokit/rest@npm:22.0.0"
|
||||
dependencies:
|
||||
"@octokit/core": "npm:^7.0.6"
|
||||
"@octokit/plugin-paginate-rest": "npm:^14.0.0"
|
||||
"@octokit/core": "npm:^7.0.2"
|
||||
"@octokit/plugin-paginate-rest": "npm:^13.0.1"
|
||||
"@octokit/plugin-request-log": "npm:^6.0.0"
|
||||
"@octokit/plugin-rest-endpoint-methods": "npm:^17.0.0"
|
||||
checksum: 10/ec2e94cfa8766716faeb3ca18527d9af746482d35aaa6e4265a30cb669ae3f31f4ebb6235edebe5ae62bc2cec2b8e88902584f698df2e7cabac3a15fd27da665
|
||||
"@octokit/plugin-rest-endpoint-methods": "npm:^16.0.0"
|
||||
checksum: 10/d2b80fefd6aed307cb728980cb1d94cb484d48fabf0055198664287a7fb50544d312b005e4fb8dec2a6e97a153ec0ad7654d62f59898e1077a4cfba64e6d5c3e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/types@npm:^14.0.0, @octokit/types@npm:^14.1.0":
|
||||
version: 14.1.0
|
||||
resolution: "@octokit/types@npm:14.1.0"
|
||||
dependencies:
|
||||
"@octokit/openapi-types": "npm:^25.1.0"
|
||||
checksum: 10/ea5549ca6176bd1184427141a77bca88c68f07d252d3ea1db7f9b58ec16b66391218a75a99927efb1e36a2cb00e8ed37a79b71fdc95a1117a9982516156fd997
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/types@npm:^15.0.0":
|
||||
version: 15.0.0
|
||||
resolution: "@octokit/types@npm:15.0.0"
|
||||
dependencies:
|
||||
"@octokit/openapi-types": "npm:^26.0.0"
|
||||
checksum: 10/c9207551ea0a56f7b740d7fed7f0eb3801abfc39b67ece6f914ffc41484879342cf909300c601ccb1ea5735737491f539c9a1464ddbc35acad323f6d78b6f17f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -8939,10 +8971,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"globals@npm:16.5.0":
|
||||
version: 16.5.0
|
||||
resolution: "globals@npm:16.5.0"
|
||||
checksum: 10/f9e8a2a13f50222c127030a619e283e7bbfe32966316bdde0715af1d15a7e40cb9c24ff52cad59671f97762ed8b515353c2f8674f560c63d9385f19ee26735a6
|
||||
"globals@npm:16.4.0":
|
||||
version: 16.4.0
|
||||
resolution: "globals@npm:16.4.0"
|
||||
checksum: 10/1627a9f42fb4c82d7af6a0c8b6cd616e00110908304d5f1ddcdf325998f3aed45a4b29d8a1e47870f328817805263e31e4f1673f00022b9c2b210552767921cf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -9184,10 +9216,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"hls.js@npm:1.6.14":
|
||||
version: 1.6.14
|
||||
resolution: "hls.js@npm:1.6.14"
|
||||
checksum: 10/56eedf163912abf72bd5ca0eac44dbaa442cff59bb8d1ff3303614bf18d21dfa3ec7b85db5b71449986c52c63b7efb6bd5392b048bf22c61549f71a001fa94cd
|
||||
"hls.js@npm:1.6.13":
|
||||
version: 1.6.13
|
||||
resolution: "hls.js@npm:1.6.13"
|
||||
checksum: 10/4de045fddbeb6edc3859021ff60b268a642aa87e83347a3a764e53299b616d57f36eecef0b91eab240ffde84f75d1ab5f7cc0916a929de265707d0f0b27d1c71
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -9261,7 +9293,7 @@ __metadata:
|
||||
"@mdi/svg": "npm:7.4.47"
|
||||
"@octokit/auth-oauth-device": "npm:8.0.3"
|
||||
"@octokit/plugin-retry": "npm:8.0.3"
|
||||
"@octokit/rest": "npm:22.0.1"
|
||||
"@octokit/rest": "npm:22.0.0"
|
||||
"@replit/codemirror-indentation-markers": "npm:6.5.3"
|
||||
"@rsdoctor/rspack-plugin": "npm:1.3.7"
|
||||
"@rspack/core": "npm:1.6.0"
|
||||
@@ -9331,7 +9363,7 @@ __metadata:
|
||||
gulp-json-transform: "npm:0.5.0"
|
||||
gulp-rename: "npm:2.1.0"
|
||||
gulp-zopfli-green: "npm:6.0.2"
|
||||
hls.js: "npm:1.6.14"
|
||||
hls.js: "npm:1.6.13"
|
||||
home-assistant-js-websocket: "npm:9.5.0"
|
||||
html-minifier-terser: "npm:7.2.0"
|
||||
husky: "npm:9.1.7"
|
||||
|
||||
Reference in New Issue
Block a user