Compare commits

..

15 Commits

Author SHA1 Message Date
Wendelin
204500c477 Add unknown item text localization to various pickers 2025-12-05 13:12:09 +01:00
Jan-Philipp Benecke
3ea4a28931 Fix underflowing text in ha-settings-row (#28339) 2025-12-05 09:08:42 +00:00
Benjamin
3b092b834e Filter out hidden entities in map configuration (#28320) 2025-12-05 08:44:07 +01:00
Aidan Timson
ed8ccbe12c Add scrollable fade mixin to ha-wa-dialog (#28346) 2025-12-05 08:43:43 +01:00
karwosts
420f88f73a Fix incorrect water & gas price hints (#28357)
* Fix incorrect water price hint

* Gas
2025-12-05 08:44:23 +02:00
karwosts
086aa5fa28 Delete stop response variable on empty (#28362) 2025-12-05 08:38:39 +02:00
renovate[bot]
cca4cc512b Update dependency @rsdoctor/rspack-plugin to v1.3.12 (#28350)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-04 14:54:27 +00:00
Petar Petrov
8eb65f327a Append current state to power-sources-graph (#28330) 2025-12-04 10:18:48 +01:00
Petar Petrov
f3495feacb Fix markdown sections and styling (#28333) 2025-12-04 10:15:11 +01:00
Petar Petrov
2161bcfa3f Fix gauge severity using entity state instead of attribute value (#28331) 2025-12-04 10:13:41 +01:00
Paul Bottein
1400398422 Move reorder areas and floors to floor overflow (#28335) 2025-12-04 10:58:27 +02:00
Preet Patel
506d466c03 Fix energy dashboard redirect for device-consumption-only configs (#28322)
When users configure energy with only device consumption (no
grid/solar/battery/gas/water sources), the dashboard would redirect
to /config/energy instead of displaying. This occurred because
_generateLovelaceConfig() returned an empty views array.

The fix adds hasDeviceConsumption check and includes ENERGY_VIEW
when device consumption is configured, since energy-view-strategy
already supports device consumption cards.
2025-12-04 06:38:43 +00:00
Bram Kragten
46735c72ed Add more info to the energy demo (#28316)
* Add more info to the energy demo

* Also add battery power
2025-12-03 20:46:59 +01:00
Copilot
c43d41053b Migrate ha-button-menu to ha-dropdown in 4 files (#28300)
Co-authored-by: wendevlin <12148533+wendevlin@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Wendelin <w@pe8.at>
Co-authored-by: uptimeZERO_ <pavilionsahota@gmail.com>
2025-12-03 16:43:07 +00:00
Petar Petrov
844d53a0ba Always show energy-sources-table in overview (#28315) 2025-12-03 17:18:07 +01:00
38 changed files with 752 additions and 493 deletions

2
.nvmrc
View File

@@ -1 +1 @@
24.11.1
22.21.1

View File

@@ -156,7 +156,9 @@ const createTestTranslation = () =>
*/
const createMasterTranslation = () =>
gulp
.src([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])])
.src([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])], {
allowEmpty: true,
})
.pipe(new CustomJSON(lokaliseTransform))
.pipe(new MergeJSON("en"))
.pipe(gulp.dest(workDir));

View File

@@ -44,18 +44,24 @@ export const mockEnergy = (hass: MockHomeAssistant) => {
number_energy_price: null,
},
],
power: [
{ stat_rate: "sensor.power_grid" },
{ stat_rate: "sensor.power_grid_return" },
],
cost_adjustment_day: 0,
},
{
type: "solar",
stat_energy_from: "sensor.solar_production",
stat_rate: "sensor.power_solar",
config_entry_solar_forecast: ["solar_forecast"],
},
/* {
{
type: "battery",
stat_energy_from: "sensor.battery_output",
stat_energy_to: "sensor.battery_input",
}, */
stat_rate: "sensor.power_battery",
},
{
type: "gas",
stat_energy_from: "sensor.energy_gas",
@@ -63,28 +69,48 @@ export const mockEnergy = (hass: MockHomeAssistant) => {
entity_energy_price: null,
number_energy_price: null,
},
{
type: "water",
stat_energy_from: "sensor.energy_water",
stat_cost: "sensor.energy_water_cost",
entity_energy_price: null,
number_energy_price: null,
},
],
device_consumption: [
{
stat_consumption: "sensor.energy_car",
stat_rate: "sensor.power_car",
},
{
stat_consumption: "sensor.energy_ac",
stat_rate: "sensor.power_ac",
},
{
stat_consumption: "sensor.energy_washing_machine",
stat_rate: "sensor.power_washing_machine",
},
{
stat_consumption: "sensor.energy_dryer",
stat_rate: "sensor.power_dryer",
},
{
stat_consumption: "sensor.energy_heat_pump",
stat_rate: "sensor.power_heat_pump",
},
{
stat_consumption: "sensor.energy_boiler",
stat_rate: "sensor.power_boiler",
},
],
device_consumption_water: [
{
stat_consumption: "sensor.water_kitchen",
},
{
stat_consumption: "sensor.water_garden",
},
],
device_consumption_water: [],
})
);
hass.mockWS(

View File

@@ -154,6 +154,38 @@ export const energyEntities = () =>
unit_of_measurement: "EUR",
},
},
"sensor.power_grid": {
entity_id: "sensor.power_grid",
state: "500",
attributes: {
state_class: "measurement",
unit_of_measurement: "W",
},
},
"sensor.power_grid_return": {
entity_id: "sensor.power_grid_return",
state: "-100",
attributes: {
state_class: "measurement",
unit_of_measurement: "W",
},
},
"sensor.power_solar": {
entity_id: "sensor.power_solar",
state: "200",
attributes: {
state_class: "measurement",
unit_of_measurement: "W",
},
},
"sensor.power_battery": {
entity_id: "sensor.power_battery",
state: "100",
attributes: {
state_class: "measurement",
unit_of_measurement: "W",
},
},
"sensor.energy_gas_cost": {
entity_id: "sensor.energy_gas_cost",
state: "2",
@@ -171,6 +203,15 @@ export const energyEntities = () =>
unit_of_measurement: "m³",
},
},
"sensor.energy_water": {
entity_id: "sensor.energy_water",
state: "4000",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Water",
unit_of_measurement: "L",
},
},
"sensor.energy_car": {
entity_id: "sensor.energy_car",
state: "4",
@@ -225,4 +266,58 @@ export const energyEntities = () =>
unit_of_measurement: "kWh",
},
},
"sensor.power_car": {
entity_id: "sensor.power_car",
state: "40",
attributes: {
state_class: "measurement",
friendly_name: "Electric car",
unit_of_measurement: "W",
},
},
"sensor.power_ac": {
entity_id: "sensor.power_ac",
state: "30",
attributes: {
state_class: "measurement",
friendly_name: "Air conditioning",
unit_of_measurement: "W",
},
},
"sensor.power_washing_machine": {
entity_id: "sensor.power_washing_machine",
state: "60",
attributes: {
state_class: "measurement",
friendly_name: "Washing machine",
unit_of_measurement: "W",
},
},
"sensor.power_dryer": {
entity_id: "sensor.power_dryer",
state: "55",
attributes: {
state_class: "measurement",
friendly_name: "Dryer",
unit_of_measurement: "W",
},
},
"sensor.power_heat_pump": {
entity_id: "sensor.power_heat_pump",
state: "60",
attributes: {
state_class: "measurement",
friendly_name: "Heat pump",
unit_of_measurement: "W",
},
},
"sensor.power_boiler": {
entity_id: "sensor.power_boiler",
state: "70",
attributes: {
state_class: "measurement",
friendly_name: "Boiler",
unit_of_measurement: "W",
},
},
});

View File

@@ -17,17 +17,15 @@ const generateMeanStatistics = (
end: Date,
// eslint-disable-next-line default-param-last
period: "5minute" | "hour" | "day" | "month" = "hour",
initValue: number,
maxDiff: number
): StatisticValue[] => {
const statistics: StatisticValue[] = [];
let currentDate = new Date(start);
currentDate.setMinutes(0, 0, 0);
let lastVal = initValue;
const now = new Date();
while (end > currentDate && currentDate < now) {
const delta = Math.random() * maxDiff;
const mean = lastVal + delta;
const mean = delta;
statistics.push({
start: currentDate.getTime(),
end: currentDate.getTime(),
@@ -38,7 +36,6 @@ const generateMeanStatistics = (
state: mean,
sum: null,
});
lastVal = mean;
currentDate =
period === "day"
? addDays(currentDate, 1)
@@ -336,7 +333,6 @@ export const mockRecorder = (mockHass: MockHomeAssistant) => {
start,
end,
period,
state,
state * (state > 80 ? 0.05 : 0.1)
);
}

View File

@@ -157,7 +157,7 @@
"@octokit/auth-oauth-device": "8.0.3",
"@octokit/plugin-retry": "8.0.3",
"@octokit/rest": "22.0.1",
"@rsdoctor/rspack-plugin": "1.3.11",
"@rsdoctor/rspack-plugin": "1.3.12",
"@rspack/core": "1.6.5",
"@rspack/dev-server": "1.1.4",
"@types/babel__plugin-transform-runtime": "7.9.5",
@@ -238,6 +238,6 @@
},
"packageManager": "yarn@4.12.0",
"volta": {
"node": "24.11.1"
"node": "22.21.1"
}
}

View File

@@ -216,6 +216,9 @@ export class HaDevicePicker extends LitElement {
.getItems=${this._getItems}
.hideClearIcon=${this.hideClearIcon}
.valueRenderer=${valueRenderer}
.unknownItemText=${this.hass.localize(
"ui.components.device-picker.unknown"
)}
@value-changed=${this._valueChanged}
>
</ha-generic-picker>

View File

@@ -288,10 +288,13 @@ export class HaEntityPicker extends LitElement {
.hideClearIcon=${this.hideClearIcon}
.searchFn=${this._searchFn}
.valueRenderer=${this._valueRenderer}
@value-changed=${this._valueChanged}
.addButtonLabel=${this.addButton
? this.hass.localize("ui.components.entity.entity-picker.add")
: undefined}
.unknownItemText=${this.hass.localize(
"ui.components.entity.entity-picker.unknown"
)}
@value-changed=${this._valueChanged}
>
</ha-generic-picker>
`;

View File

@@ -475,6 +475,9 @@ export class HaStatisticPicker extends LitElement {
.searchFn=${this._searchFn}
.valueRenderer=${this._valueRenderer}
.helper=${this.helper}
.unknownItemText=${this.hass.localize(
"ui.components.statistic-picker.unknown"
)}
@value-changed=${this._valueChanged}
>
</ha-generic-picker>

View File

@@ -379,6 +379,9 @@ export class HaAreaPicker extends LitElement {
.getAdditionalItems=${this._getAdditionalItems}
.valueRenderer=${valueRenderer}
.addButtonLabel=${this.addButtonLabel}
.unknownItemText=${this.hass.localize(
"ui.components.area-picker.unknown"
)}
@value-changed=${this._valueChanged}
>
</ha-generic-picker>

View File

@@ -393,6 +393,9 @@ export class HaFloorPicker extends LitElement {
.getAdditionalItems=${this._getAdditionalItems}
.valueRenderer=${valueRenderer}
.rowRenderer=${this._rowRenderer}
.unknownItemText=${this.hass.localize(
"ui.components.floor-picker.unknown"
)}
@value-changed=${this._valueChanged}
>
</ha-generic-picker>

View File

@@ -4,6 +4,7 @@ import { mdiPlaylistPlus } from "@mdi/js";
import { css, html, LitElement, nothing, type CSSResultGroup } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import memoizeOne from "memoize-one";
import { tinykeys } from "tinykeys";
import { fireEvent } from "../common/dom/fire_event";
import type { HomeAssistant } from "../types";
@@ -107,6 +108,8 @@ export class HaGenericPicker extends LitElement {
@property({ attribute: "selected-section" }) public selectedSection?: string;
@property({ attribute: "unknown-item-text" }) public unknownItemText;
@query(".container") private _containerElement?: HTMLDivElement;
@query("ha-picker-combo-box") private _comboBox?: HaPickerComboBox;
@@ -156,6 +159,8 @@ export class HaGenericPicker extends LitElement {
type="button"
class=${this._opened ? "opened" : ""}
compact
.unknown=${this._unknownValue(this.value, this.getItems)}
.unknownItemText=${this.unknownItemText}
aria-label=${ifDefined(this.label)}
@click=${this.open}
@clear=${this._clear}
@@ -233,6 +238,16 @@ export class HaGenericPicker extends LitElement {
`;
}
private _unknownValue = memoizeOne(
(value?: string, getItems?: () => any[]) => {
if (value === undefined || !getItems) {
return false;
}
return !getItems().some((item) => item.id === value);
}
);
private _renderHelper() {
return this.helper
? html`<ha-input-helper-text .disabled=${this.disabled}

View File

@@ -99,10 +99,7 @@ class HaMarkdownElement extends ReactiveElement {
}
);
render(
elements.map((e) => h(unsafeHTML(e))),
this.renderRoot
);
render(h(unsafeHTML(elements.join(""))), this.renderRoot);
this._resize();

View File

@@ -25,11 +25,11 @@ export class HaMarkdown extends LitElement {
@property({ type: Boolean }) public cache = false;
@query("ha-markdown-element") private _markdownElement!: ReactiveElement;
@query("ha-markdown-element") private _markdownElement?: ReactiveElement;
protected async getUpdateComplete() {
const result = await super.getUpdateComplete();
await this._markdownElement.updateComplete;
await this._markdownElement?.updateComplete;
return result;
}

View File

@@ -1,3 +1,4 @@
import { consume } from "@lit/context";
import { mdiClose, mdiMenuDown } from "@mdi/js";
import {
css,
@@ -7,8 +8,10 @@ import {
type CSSResultGroup,
type TemplateResult,
} from "lit";
import { customElement, property, query } from "lit/decorators";
import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { localizeContext } from "../data/context";
import type { HomeAssistant } from "../types";
import "./ha-combo-box-item";
import type { HaComboBoxItem } from "./ha-combo-box-item";
import "./ha-icon-button";
@@ -33,6 +36,10 @@ export class HaPickerField extends LitElement {
@property() public placeholder?: string;
@property({ type: Boolean, reflect: true }) public unknown = false;
@property({ attribute: "unknown-item-text" }) public unknownItemText;
@property({ attribute: "hide-clear-icon", type: Boolean })
public hideClearIcon = false;
@@ -41,6 +48,10 @@ export class HaPickerField extends LitElement {
@query("ha-combo-box-item", true) public item!: HaComboBoxItem;
@state()
@consume({ context: localizeContext, subscribe: true })
private localize!: HomeAssistant["localize"];
public async focus() {
await this.updateComplete;
await this.item?.focus();
@@ -61,6 +72,12 @@ export class HaPickerField extends LitElement {
${this.placeholder}
</span>
`}
${this.unknown
? html`<div slot="supporting-text" class="unknown">
${this.unknownItemText ||
this.localize("ui.components.combo-box.unknown_item")}
</div>`
: nothing}
${showClearIcon
? html`
<ha-icon-button
@@ -142,6 +159,10 @@ export class HaPickerField extends LitElement {
background-color: var(--mdc-theme-primary);
}
:host([unknown]) ha-combo-box-item {
background-color: var(--ha-color-fill-warning-quiet-resting);
}
.clear {
margin: 0 -8px;
--mdc-icon-button-size: 32px;
@@ -156,6 +177,10 @@ export class HaPickerField extends LitElement {
color: var(--secondary-text-color);
padding: 0 8px;
}
.unknown {
color: var(--ha-color-on-warning-normal);
}
`,
];
}

View File

@@ -141,6 +141,9 @@ class HaServicePicker extends LitElement {
this.hass.localize,
this.hass.services
)}
.unknownItemText=${this.hass.localize(
"ui.components.service-picker.unknown"
)}
@value-changed=${this._valueChanged}
>
</ha-generic-picker>

View File

@@ -86,7 +86,7 @@ export class HaSettingsRow extends LitElement {
display: contents;
}
:host(:not([narrow])) .content {
display: var(--settings-row-content-display, flex);
display: var(--settings-row-content-display, contents);
justify-content: flex-end;
flex: 1;
min-width: 0;
@@ -109,6 +109,7 @@ export class HaSettingsRow extends LitElement {
}
.prefix-wrap {
display: var(--settings-row-prefix-display);
flex-grow: 1;
}
:host([narrow]) .prefix-wrap {
display: flex;

View File

@@ -10,6 +10,7 @@ import {
} from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { fireEvent } from "../common/dom/fire_event";
import { ScrollableFadeMixin } from "../mixins/scrollable-fade-mixin";
import { haStyleScrollbar } from "../resources/styles";
import type { HomeAssistant } from "../types";
import "./ha-dialog-header";
@@ -72,7 +73,7 @@ export type DialogWidth = "small" | "medium" | "large" | "full";
* @see https://github.com/home-assistant/frontend/issues/27143
*/
@customElement("ha-wa-dialog")
export class HaWaDialog extends LitElement {
export class HaWaDialog extends ScrollableFadeMixin(LitElement) {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: "aria-labelledby" })
@@ -113,6 +114,10 @@ export class HaWaDialog extends LitElement {
@state()
private _bodyScrolled = false;
protected get scrollableElement(): HTMLElement | null {
return this.bodyContainer;
}
protected updated(
changedProperties: Map<string | number | symbol, unknown>
): void {
@@ -161,8 +166,11 @@ export class HaWaDialog extends LitElement {
<slot name="headerActionItems" slot="actionItems"></slot>
</ha-dialog-header>
</slot>
<div class="body ha-scrollbar" @scroll=${this._handleBodyScroll}>
<slot></slot>
<div class="content-wrapper">
<div class="body ha-scrollbar" @scroll=${this._handleBodyScroll}>
<slot></slot>
</div>
${this.renderScrollableFades()}
</div>
<slot name="footer" slot="footer"></slot>
</wa-dialog>
@@ -199,165 +207,179 @@ export class HaWaDialog extends LitElement {
this._bodyScrolled = (ev.target as HTMLDivElement).scrollTop > 0;
}
static styles = [
haStyleScrollbar,
css`
wa-dialog {
--full-width: var(--ha-dialog-width-full, min(95vw, var(--safe-width)));
--width: min(var(--ha-dialog-width-md, 580px), var(--full-width));
--spacing: var(--dialog-content-padding, var(--ha-space-6));
--show-duration: var(--ha-dialog-show-duration, 200ms);
--hide-duration: var(--ha-dialog-hide-duration, 200ms);
--ha-dialog-surface-background: var(
--card-background-color,
var(--ha-color-surface-default)
);
--wa-color-surface-raised: var(
--ha-dialog-surface-background,
var(--card-background-color, var(--ha-color-surface-default))
);
--wa-panel-border-radius: var(
--ha-dialog-border-radius,
var(--ha-border-radius-3xl)
);
max-width: var(--ha-dialog-max-width, var(--safe-width));
}
static get styles() {
return [
...super.styles,
haStyleScrollbar,
css`
wa-dialog {
--full-width: var(
--ha-dialog-width-full,
min(95vw, var(--safe-width))
);
--width: min(var(--ha-dialog-width-md, 580px), var(--full-width));
--spacing: var(--dialog-content-padding, var(--ha-space-6));
--show-duration: var(--ha-dialog-show-duration, 200ms);
--hide-duration: var(--ha-dialog-hide-duration, 200ms);
--ha-dialog-surface-background: var(
--card-background-color,
var(--ha-color-surface-default)
);
--wa-color-surface-raised: var(
--ha-dialog-surface-background,
var(--card-background-color, var(--ha-color-surface-default))
);
--wa-panel-border-radius: var(
--ha-dialog-border-radius,
var(--ha-border-radius-3xl)
);
max-width: var(--ha-dialog-max-width, var(--safe-width));
}
:host([width="small"]) wa-dialog {
--width: min(var(--ha-dialog-width-sm, 320px), var(--full-width));
}
:host([width="small"]) wa-dialog {
--width: min(var(--ha-dialog-width-sm, 320px), var(--full-width));
}
:host([width="large"]) wa-dialog {
--width: min(var(--ha-dialog-width-lg, 1024px), var(--full-width));
}
:host([width="large"]) wa-dialog {
--width: min(var(--ha-dialog-width-lg, 1024px), var(--full-width));
}
:host([width="full"]) wa-dialog {
--width: var(--full-width);
}
:host([width="full"]) wa-dialog {
--width: var(--full-width);
}
wa-dialog::part(dialog) {
min-width: var(--width, var(--full-width));
max-width: var(--width, var(--full-width));
max-height: var(
--ha-dialog-max-height,
calc(var(--safe-height) - var(--ha-space-20))
);
min-height: var(--ha-dialog-min-height);
margin-top: var(--dialog-surface-margin-top, auto);
/* Used to offset the dialog from the safe areas when space is limited */
transform: translate(
calc(
var(--safe-area-offset-left, var(--ha-space-0)) - var(
--safe-area-offset-right,
var(--ha-space-0)
)
),
calc(
var(--safe-area-offset-top, var(--ha-space-0)) - var(
--safe-area-offset-bottom,
var(--ha-space-0)
)
)
);
display: flex;
flex-direction: column;
overflow: hidden;
}
wa-dialog::part(dialog) {
min-width: var(--width, var(--full-width));
max-width: var(--width, var(--full-width));
max-height: var(
--ha-dialog-max-height,
calc(var(--safe-height) - var(--ha-space-20))
);
min-height: var(--ha-dialog-min-height);
margin-top: var(--dialog-surface-margin-top, auto);
/* Used to offset the dialog from the safe areas when space is limited */
transform: translate(
calc(
var(--safe-area-offset-left, var(--ha-space-0)) - var(
--safe-area-offset-right,
var(--ha-space-0)
)
),
calc(
var(--safe-area-offset-top, var(--ha-space-0)) - var(
--safe-area-offset-bottom,
var(--ha-space-0)
)
)
);
display: flex;
flex-direction: column;
overflow: hidden;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
:host([type="standard"]) {
--ha-dialog-border-radius: var(--ha-space-0);
@media all and (max-width: 450px), all and (max-height: 500px) {
:host([type="standard"]) {
--ha-dialog-border-radius: var(--ha-space-0);
wa-dialog {
/* Make the container fill the whole screen width and not the safe width */
--full-width: var(--ha-dialog-width-full, 100vw);
--width: var(--full-width);
}
wa-dialog {
/* Make the container fill the whole screen width and not the safe width */
--full-width: var(--ha-dialog-width-full, 100vw);
--width: var(--full-width);
}
wa-dialog::part(dialog) {
/* Make the dialog fill the whole screen height and not the safe height */
min-height: var(--ha-dialog-min-height, 100vh);
min-height: var(--ha-dialog-min-height, 100dvh);
max-height: var(--ha-dialog-max-height, 100vh);
max-height: var(--ha-dialog-max-height, 100dvh);
margin-top: 0;
margin-bottom: 0;
/* Use safe area as padding instead of the container size */
padding-top: var(--safe-area-inset-top);
padding-bottom: var(--safe-area-inset-bottom);
padding-left: var(--safe-area-inset-left);
padding-right: var(--safe-area-inset-right);
/* Reset the transform to center the dialog */
transform: none;
wa-dialog::part(dialog) {
/* Make the dialog fill the whole screen height and not the safe height */
min-height: var(--ha-dialog-min-height, 100vh);
min-height: var(--ha-dialog-min-height, 100dvh);
max-height: var(--ha-dialog-max-height, 100vh);
max-height: var(--ha-dialog-max-height, 100dvh);
margin-top: 0;
margin-bottom: 0;
/* Use safe area as padding instead of the container size */
padding-top: var(--safe-area-inset-top);
padding-bottom: var(--safe-area-inset-bottom);
padding-left: var(--safe-area-inset-left);
padding-right: var(--safe-area-inset-right);
/* Reset the transform to center the dialog */
transform: none;
}
}
}
}
.header-title-container {
display: flex;
align-items: center;
}
.header-title-container {
display: flex;
align-items: center;
}
.header-title {
margin: 0;
margin-bottom: 0;
color: var(--ha-dialog-header-title-color, var(--primary-text-color));
font-size: var(
--ha-dialog-header-title-font-size,
var(--ha-font-size-2xl)
);
line-height: var(
--ha-dialog-header-title-line-height,
var(--ha-line-height-condensed)
);
font-weight: var(
--ha-dialog-header-title-font-weight,
var(--ha-font-weight-normal)
);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: var(--ha-space-3);
}
.header-title {
margin: 0;
margin-bottom: 0;
color: var(--ha-dialog-header-title-color, var(--primary-text-color));
font-size: var(
--ha-dialog-header-title-font-size,
var(--ha-font-size-2xl)
);
line-height: var(
--ha-dialog-header-title-line-height,
var(--ha-line-height-condensed)
);
font-weight: var(
--ha-dialog-header-title-font-weight,
var(--ha-font-weight-normal)
);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: var(--ha-space-3);
}
wa-dialog::part(body) {
padding: 0;
display: flex;
flex-direction: column;
max-width: 100%;
overflow: hidden;
}
wa-dialog::part(body) {
padding: 0;
display: flex;
flex-direction: column;
max-width: 100%;
overflow: hidden;
}
.body {
position: var(--dialog-content-position, relative);
padding: 0 var(--dialog-content-padding, var(--ha-space-6))
var(--dialog-content-padding, var(--ha-space-6))
var(--dialog-content-padding, var(--ha-space-6));
overflow: auto;
flex-grow: 1;
}
:host([flexcontent]) .body {
max-width: 100%;
flex: 1;
display: flex;
flex-direction: column;
}
.content-wrapper {
position: relative;
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
wa-dialog::part(footer) {
padding: var(--ha-space-0);
}
.body {
position: var(--dialog-content-position, relative);
padding: 0 var(--dialog-content-padding, var(--ha-space-6))
var(--dialog-content-padding, var(--ha-space-6))
var(--dialog-content-padding, var(--ha-space-6));
overflow: auto;
flex-grow: 1;
}
:host([flexcontent]) .body {
max-width: 100%;
flex: 1;
display: flex;
flex-direction: column;
}
::slotted([slot="footer"]) {
display: flex;
padding: var(--ha-space-3) var(--ha-space-4) var(--ha-space-4)
var(--ha-space-4);
gap: var(--ha-space-3);
justify-content: flex-end;
align-items: center;
width: 100%;
}
`,
];
wa-dialog::part(footer) {
padding: var(--ha-space-0);
}
::slotted([slot="footer"]) {
display: flex;
padding: var(--ha-space-3) var(--ha-space-4) var(--ha-space-4)
var(--ha-space-4);
gap: var(--ha-space-3);
justify-content: flex-end;
align-items: center;
width: 100%;
}
`,
];
}
}
declare global {

View File

@@ -134,6 +134,9 @@ class HaUserPicker extends LitElement {
.getItems=${this._getItems}
.valueRenderer=${this._valueRenderer}
.rowRenderer=${this._rowRenderer}
.unknownItemText=${this.hass.localize(
"ui.components.user-picker.unknown"
)}
@value-changed=${this._valueChanged}
>
</ha-generic-picker>

View File

@@ -11,7 +11,7 @@ import {
isLastDayOfMonth,
addYears,
} from "date-fns";
import type { Collection } from "home-assistant-js-websocket";
import type { Collection, HassEntity } from "home-assistant-js-websocket";
import { getCollection } from "home-assistant-js-websocket";
import memoizeOne from "memoize-one";
import {
@@ -1361,3 +1361,37 @@ export const calculateSolarConsumedGauge = (
}
return undefined;
};
/**
* Get current power value from entity state, normalized to kW
* @param stateObj - The entity state object to get power value from
* @returns Power value in kW, or 0 if entity not found or invalid
*/
export const getPowerFromState = (stateObj: HassEntity): number | undefined => {
if (!stateObj) {
return undefined;
}
const value = parseFloat(stateObj.state);
if (isNaN(value)) {
return undefined;
}
// Normalize to kW based on unit of measurement (case-sensitive)
// Supported units: GW, kW, MW, mW, TW, W
const unit = stateObj.attributes.unit_of_measurement;
switch (unit) {
case "W":
return value / 1000;
case "mW":
return value / 1000000;
case "MW":
return value * 1000;
case "GW":
return value * 1000000;
case "TW":
return value * 1000000000;
default:
// Assume kW if no unit or unit is kW
return value;
}
};

View File

@@ -81,8 +81,11 @@ class DialogAreasFloorsOrder extends LitElement {
return nothing;
}
const hasFloors = this._hierarchy.floors.length > 0;
const dialogTitle = this.hass.localize(
"ui.panel.config.areas.dialog.reorder_title"
hasFloors
? "ui.panel.config.areas.dialog.reorder_floors_areas_title"
: "ui.panel.config.areas.dialog.reorder_areas_title"
);
return html`
@@ -418,7 +421,6 @@ class DialogAreasFloorsOrder extends LitElement {
}
.floor.unassigned {
border-style: dashed;
margin-top: 16px;
}

View File

@@ -175,21 +175,12 @@ export class HaConfigAreasDashboard extends LitElement {
.route=${this.route}
has-fab
>
<ha-button-menu slot="toolbar-icon">
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-list-item graphic="icon" @click=${this._showReorderDialog}>
<ha-svg-icon .path=${mdiSort} slot="graphic"></ha-svg-icon>
${this.hass.localize("ui.panel.config.areas.picker.reorder")}
</ha-list-item>
<ha-list-item graphic="icon" @click=${this._showHelp}>
<ha-svg-icon .path=${mdiHelpCircle} slot="graphic"></ha-svg-icon>
${this.hass.localize("ui.common.help")}
</ha-list-item>
</ha-button-menu>
<ha-icon-button
slot="toolbar-icon"
.label=${this.hass.localize("ui.common.help")}
.path=${mdiHelpCircle}
@click=${this._showHelp}
></ha-icon-button>
<div class="container">
<div class="floors">
${this._hierarchy.floors.map(({ areas, id }) => {
@@ -213,6 +204,16 @@ export class HaConfigAreasDashboard extends LitElement {
slot="trigger"
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-list-item graphic="icon"
><ha-svg-icon
.path=${mdiSort}
slot="graphic"
></ha-svg-icon
>${this.hass.localize(
"ui.panel.config.areas.picker.reorder"
)}</ha-list-item
>
<li divider role="separator"></li>
<ha-list-item graphic="icon"
><ha-svg-icon
.path=${mdiPencil}
@@ -266,9 +267,30 @@ export class HaConfigAreasDashboard extends LitElement {
<div class="header">
<h2>
${this.hass.localize(
"ui.panel.config.areas.picker.other_areas"
this._hierarchy.floors.length
? "ui.panel.config.areas.picker.other_areas"
: "ui.panel.config.areas.picker.header"
)}
</h2>
<div class="actions">
<ha-button-menu
@action=${this._handleUnassignedAreasAction}
>
<ha-icon-button
slot="trigger"
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-list-item graphic="icon"
><ha-svg-icon
.path=${mdiSort}
slot="graphic"
></ha-svg-icon
>${this.hass.localize(
"ui.panel.config.areas.picker.reorder"
)}</ha-list-item
>
</ha-button-menu>
</div>
</div>
<ha-sortable
handle-selector="a"
@@ -515,14 +537,23 @@ export class HaConfigAreasDashboard extends LitElement {
const floor = (ev.currentTarget as any).floor;
switch (ev.detail.index) {
case 0:
this._editFloor(floor);
this._showReorderDialog();
break;
case 1:
this._editFloor(floor);
break;
case 2:
this._deleteFloor(floor);
break;
}
}
private _handleUnassignedAreasAction(ev: CustomEvent<ActionDetail>) {
if (ev.detail.index === 0) {
this._showReorderDialog();
}
}
private _createFloor() {
this._openFloorDialog();
}

View File

@@ -64,8 +64,15 @@ export class HaStopAction extends LitElement implements ActionElement {
private _responseChanged(ev: Event) {
ev.stopPropagation();
const newAction = { ...this.action };
const newValue = (ev.target as any).value;
if (newValue) {
newAction.response_variable = newValue;
} else {
delete newAction.response_variable;
}
fireEvent(this, "value-changed", {
value: { ...this.action, response_variable: (ev.target as any).value },
value: newAction,
});
}

View File

@@ -203,6 +203,9 @@ export class HaCategoryPicker extends SubscribeMixin(LitElement) {
.getItems=${this._getItems}
.getAdditionalItems=${this._getAdditionalItems}
.valueRenderer=${valueRenderer}
.unknownItemText=${this.hass.localize(
"ui.components.category-picker.unknown"
)}
@value-changed=${this._valueChanged}
>
</ha-generic-picker>

View File

@@ -9,6 +9,7 @@ import "../../../../components/ha-dialog";
import "../../../../components/ha-formfield";
import "../../../../components/ha-radio";
import "../../../../components/ha-button";
import "../../../../components/ha-markdown";
import type { HaRadio } from "../../../../components/ha-radio";
import "../../../../components/ha-textfield";
import type { GasSourceTypeEnergyPreference } from "../../../../data/energy";
@@ -109,6 +110,15 @@ export class DialogEnergyGasSettings
? `${this.hass.config.currency}/${this._pickedDisplayUnit}`
: undefined;
const pickedUnitClass =
this._pickedDisplayUnit &&
this._energy_units?.includes(this._pickedDisplayUnit)
? "energy"
: this._pickedDisplayUnit &&
this._gas_units?.includes(this._pickedDisplayUnit)
? "volume"
: undefined;
const externalSource =
this._source.stat_energy_from &&
isExternalStatistic(this._source.stat_energy_from);
@@ -213,9 +223,33 @@ export class DialogEnergyGasSettings
.hass=${this.hass}
include-domains='["sensor", "input_number"]'
.value=${this._source.entity_energy_price}
.label=${`${this.hass.localize(
.label=${this.hass.localize(
"ui.panel.config.energy.gas.dialog.cost_entity_input"
)} ${unitPrice ? ` (${unitPrice})` : ""}`}
)}
.helper=${pickedUnitClass
? html`<ha-markdown
.content=${this.hass.localize(
"ui.panel.config.energy.gas.dialog.cost_entity_helper",
pickedUnitClass === "energy"
? {
currency: this.hass.config.currency,
class: this.hass.localize(
"ui.panel.config.energy.gas.dialog.cost_entity_helper_energy"
),
unit1: "kWh",
unit2: "Wh",
}
: {
currency: this.hass.config.currency,
class: this.hass.localize(
"ui.panel.config.energy.gas.dialog.cost_entity_helper_volume"
),
unit1: "m³",
unit2: "ft³",
}
)}
></ha-markdown>`
: nothing}
@value-changed=${this._priceEntityChanged}
></ha-entity-picker>`
: ""}

View File

@@ -9,6 +9,7 @@ import "../../../../components/ha-dialog";
import "../../../../components/ha-button";
import "../../../../components/ha-formfield";
import "../../../../components/ha-radio";
import "../../../../components/ha-markdown";
import type { HaRadio } from "../../../../components/ha-radio";
import "../../../../components/ha-textfield";
import type { WaterSourceTypeEnergyPreference } from "../../../../data/energy";
@@ -16,11 +17,7 @@ import {
emptyWaterEnergyPreference,
energyStatisticHelpUrl,
} from "../../../../data/energy";
import {
getDisplayUnit,
getStatisticMetadata,
isExternalStatistic,
} from "../../../../data/recorder";
import { isExternalStatistic } from "../../../../data/recorder";
import { getSensorDeviceClassConvertibleUnits } from "../../../../data/sensor";
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
import { haStyle, haStyleDialog } from "../../../../resources/styles";
@@ -40,8 +37,6 @@ export class DialogEnergyWaterSettings
@state() private _costs?: "no-costs" | "number" | "entity" | "statistic";
@state() private _pickedDisplayUnit?: string | null;
@state() private _water_units?: string[];
@state() private _error?: string;
@@ -55,11 +50,6 @@ export class DialogEnergyWaterSettings
this._source = params.source
? { ...params.source }
: emptyWaterEnergyPreference();
this._pickedDisplayUnit = getDisplayUnit(
this.hass,
params.source?.stat_energy_from,
params.metadata
);
this._costs = this._source.entity_energy_price
? "entity"
: this._source.number_energy_price
@@ -79,7 +69,6 @@ export class DialogEnergyWaterSettings
this._params = undefined;
this._source = undefined;
this._error = undefined;
this._pickedDisplayUnit = undefined;
this._excludeList = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
return true;
@@ -92,10 +81,6 @@ export class DialogEnergyWaterSettings
const pickableUnit = this._water_units?.join(", ") || "";
const unitPriceSensor = this._pickedDisplayUnit
? `${this.hass.config.currency}/${this._pickedDisplayUnit}`
: undefined;
const unitPriceFixed = `${this.hass.config.currency}/${
this.hass.config.unit_system.volume === "gal" ? "gal" : "m³"
}`;
@@ -202,9 +187,15 @@ export class DialogEnergyWaterSettings
.hass=${this.hass}
include-domains='["sensor", "input_number"]'
.value=${this._source.entity_energy_price}
.label=${`${this.hass.localize(
.label=${this.hass.localize(
"ui.panel.config.energy.water.dialog.cost_entity_input"
)}${unitPriceSensor ? ` (${unitPriceSensor})` : ""}`}
)}
.helper=${html`<ha-markdown
.content=${this.hass.localize(
"ui.panel.config.energy.water.dialog.cost_entity_helper",
{ currency: this.hass.config.currency }
)}
></ha-markdown>`}
@value-changed=${this._priceEntityChanged}
></ha-entity-picker>`
: ""}
@@ -287,16 +278,6 @@ export class DialogEnergyWaterSettings
}
private async _statisticChanged(ev: CustomEvent<{ value: string }>) {
if (ev.detail.value) {
const metadata = await getStatisticMetadata(this.hass, [ev.detail.value]);
this._pickedDisplayUnit = getDisplayUnit(
this.hass,
ev.detail.value,
metadata[0]
);
} else {
this._pickedDisplayUnit = undefined;
}
if (isExternalStatistic(ev.detail.value) && this._costs !== "statistic") {
this._costs = "no-costs";
}

View File

@@ -268,8 +268,10 @@ class PanelEnergy extends LitElement {
(source) => source.type === "gas"
);
const hasDeviceConsumption = this._prefs.device_consumption.length > 0;
const views: LovelaceViewConfig[] = [];
if (hasEnergy) {
if (hasEnergy || hasDeviceConsumption) {
views.push(ENERGY_VIEW);
}
if (hasGas) {

View File

@@ -8,14 +8,6 @@ import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strat
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../ha-panel-energy";
const sourceHasCost = (source: Record<string, any>): boolean =>
Boolean(
source.stat_cost ||
source.stat_compensation ||
source.entity_energy_price ||
source.number_energy_price
);
@customElement("energy-overview-view-strategy")
export class EnergyOverviewViewStrategy extends ReactiveElement {
static async generate(
@@ -68,13 +60,6 @@ export class EnergyOverviewViewStrategy extends ReactiveElement {
(source.type === "battery" && source.stat_rate) ||
(source.type === "grid" && source.power?.length)
);
const hasCost = prefs.energy_sources.some(
(source) =>
sourceHasCost(source) ||
(source.type === "grid" &&
(source.flow_from?.some(sourceHasCost) ||
source.flow_to?.some(sourceHasCost)))
);
const overviewSection: LovelaceSectionConfig = {
type: "grid",
@@ -95,7 +80,7 @@ export class EnergyOverviewViewStrategy extends ReactiveElement {
}
view.sections!.push(overviewSection);
if (hasCost) {
if (prefs.energy_sources.length) {
view.sections!.push({
type: "grid",
cards: [

View File

@@ -6,7 +6,10 @@ import { classMap } from "lit/directives/class-map";
import "../../../../components/ha-card";
import "../../../../components/ha-svg-icon";
import type { EnergyData, EnergyPreferences } from "../../../../data/energy";
import { getEnergyDataCollection } from "../../../../data/energy";
import {
getEnergyDataCollection,
getPowerFromState,
} from "../../../../data/energy";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../../../types";
import type { LovelaceCard, LovelaceGridOptions } from "../../types";
@@ -724,33 +727,7 @@ class HuiPowerSankeyCard
// Track this entity for state change detection
this._entities.add(entityId);
const stateObj = this.hass.states[entityId];
if (!stateObj) {
return 0;
}
const value = parseFloat(stateObj.state);
if (isNaN(value)) {
return 0;
}
// Normalize to kW based on unit of measurement (case-sensitive)
// Supported units: GW, kW, MW, mW, TW, W
const unit = stateObj.attributes.unit_of_measurement;
switch (unit) {
case "W":
return value / 1000;
case "mW":
return value / 1000000;
case "MW":
return value * 1000;
case "GW":
return value * 1000000;
case "TW":
return value * 1000000000;
default:
// Assume kW if no unit or unit is kW
return value;
}
return getPowerFromState(this.hass.states[entityId]) ?? 0;
}
/**

View File

@@ -10,7 +10,10 @@ import { LinearGradient } from "../../../../resources/echarts/echarts";
import "../../../../components/chart/ha-chart-base";
import "../../../../components/ha-card";
import type { EnergyData } from "../../../../data/energy";
import { getEnergyDataCollection } from "../../../../data/energy";
import {
getEnergyDataCollection,
getPowerFromState,
} from "../../../../data/energy";
import type { StatisticValue } from "../../../../data/recorder";
import type { FrontendLocaleData } from "../../../../data/translation";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
@@ -197,6 +200,7 @@ export class HuiPowerSourcesGraphCard
},
};
const now = Date.now();
Object.keys(statIds).forEach((key, keyIndex) => {
if (statIds[key].stats.length) {
const colorHex = computedStyles.getPropertyValue(statIds[key].color);
@@ -204,7 +208,14 @@ export class HuiPowerSourcesGraphCard
// Echarts is supposed to handle that but it is bugged when you use it together with stacking.
// The interpolation breaks the stacking, so this positive/negative is a workaround
const { positive, negative } = this._processData(
statIds[key].stats.map((id: string) => energyData.stats[id] ?? [])
statIds[key].stats.map((id: string) => {
const stats = energyData.stats[id] ?? [];
const currentState = getPowerFromState(this.hass.states[id]);
if (currentState !== undefined) {
stats.push({ start: now, end: now, mean: currentState });
}
return stats;
})
);
datasets.push({
...commonSeriesOptions,

View File

@@ -96,8 +96,6 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
`;
}
const entityState = Number(stateObj.state);
if (stateObj.state === UNAVAILABLE) {
return html`
<hui-warning
@@ -164,7 +162,7 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
.unit_of_measurement ||
""}
style=${styleMap({
"--gauge-color": this._computeSeverity(entityState),
"--gauge-color": this._computeSeverity(Number(valueToDisplay)),
})}
.needle=${this._config!.needle}
.levels=${this._config!.needle ? this._severityLevels() : undefined}

View File

@@ -94,7 +94,10 @@ class HuiMapCard extends LitElement implements LovelaceCard {
}
});
return locationEntities.filter((entity) => !personSources.has(entity));
return locationEntities.filter(
(entityId) =>
!hass.entities?.[entityId]?.hidden && !personSources.has(entityId)
);
}
public setConfig(config: MapCardConfig): void {

View File

@@ -1,5 +1,4 @@
import type { List } from "@material/mwc-list/mwc-list";
import type { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import {
mdiClock,
mdiDelete,
@@ -18,15 +17,16 @@ import { classMap } from "lit/directives/class-map";
import { repeat } from "lit/directives/repeat";
import memoizeOne from "memoize-one";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { supportsFeature } from "../../../common/entity/supports-feature";
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
import "../../../components/ha-card";
import "../../../components/ha-check-list-item";
import "../../../components/ha-checkbox";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-icon-button";
import "../../../components/ha-list";
import "../../../components/ha-list-item";
import "../../../components/ha-markdown-element";
import "../../../components/ha-relative-time";
import "../../../components/ha-select";
@@ -378,28 +378,29 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
${this._todoListSupportsFeature(
TodoListEntityFeature.DELETE_TODO_ITEM
)
? html`<ha-button-menu
@closed=${stopPropagation}
fixed
@action=${this._handleCompletedMenuAction}
? html`<ha-dropdown
@wa-select=${this._handleCompletedMenuSelect}
placement="bottom-end"
>
<ha-icon-button
slot="trigger"
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-list-item graphic="icon" class="warning">
<ha-dropdown-item
value="clear"
variant="danger"
>
${this.hass!.localize(
"ui.panel.lovelace.cards.todo-list.clear_items"
)}
<ha-svg-icon
class="warning"
slot="graphic"
slot="icon"
.path=${mdiDeleteSweep}
.disabled=${unavailable}
>
</ha-svg-icon>
</ha-list-item>
</ha-button-menu>`
</ha-dropdown-item>
</ha-dropdown>`
: nothing}
</div>`
: nothing}
@@ -413,33 +414,27 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
`;
}
private _renderMenu(config: TodoListCardConfig, unavailable: boolean) {
private _renderMenu(config: TodoListCardConfig, _unavailable: boolean) {
return (!config.display_order ||
config.display_order === TodoSortMode.NONE) &&
this._todoListSupportsFeature(TodoListEntityFeature.MOVE_TODO_ITEM)
? html`<ha-button-menu
@closed=${stopPropagation}
fixed
@action=${this._handlePrimaryMenuAction}
? html`<ha-dropdown
@wa-select=${this._handlePrimaryMenuSelect}
placement="bottom-end"
>
<ha-icon-button
slot="trigger"
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-list-item graphic="icon">
<ha-dropdown-item value="reorder">
${this.hass!.localize(
this._reordering
? "ui.panel.lovelace.cards.todo-list.exit_reorder_items"
: "ui.panel.lovelace.cards.todo-list.reorder_items"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiSort}
.disabled=${unavailable}
>
</ha-svg-icon>
</ha-list-item>
</ha-button-menu>`
<ha-svg-icon slot="icon" .path=${mdiSort}> </ha-svg-icon>
</ha-dropdown-item>
</ha-dropdown>`
: nothing;
}
@@ -641,11 +636,11 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
}
}
private _handleCompletedMenuAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
this._clearCompletedItems();
break;
private _handleCompletedMenuSelect(
ev: CustomEvent<{ item: HaDropdownItem }>
) {
if (ev.detail?.item?.value === "clear") {
this._clearCompletedItems();
}
}
@@ -704,11 +699,9 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
}
}
private _handlePrimaryMenuAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
this._toggleReorder();
break;
private _handlePrimaryMenuSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
if (ev.detail?.item?.value === "reorder") {
this._toggleReorder();
}
}

View File

@@ -1,3 +1,4 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import {
mdiContentCopy,
mdiContentCut,
@@ -12,9 +13,10 @@ import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-button-menu";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-icon-button";
import "../../../components/ha-list-item";
import "../../../components/ha-svg-icon";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
@@ -41,9 +43,6 @@ export class HuiCardEditMode extends LitElement {
@property({ type: Boolean, attribute: "no-move" })
public noMove = false;
@state()
public _menuOpened = false;
@state()
public _hover = false;
@@ -91,8 +90,7 @@ export class HuiCardEditMode extends LitElement {
};
protected render(): TemplateResult {
const showOverlay =
(this._hover || this._menuOpened || this._focused) && !this.hiddenOverlay;
const showOverlay = (this._hover || this._focused) && !this.hiddenOverlay;
return html`
<div class="card-wrapper" inert><slot></slot></div>
@@ -115,107 +113,71 @@ export class HuiCardEditMode extends LitElement {
<ha-svg-icon .path=${mdiPencil}> </ha-svg-icon>
</div>
`}
<ha-button-menu
<ha-dropdown
class="more"
corner="BOTTOM_END"
menu-corner="END"
.path=${[this.path!]}
@action=${this._handleAction}
@opened=${this._handleOpened}
@closed=${this._handleClosed}
placement="bottom-end"
@wa-select=${this._handleDropdownSelect}
>
<ha-icon-button slot="trigger" .path=${mdiDotsVertical}>
</ha-icon-button>
${this.noEdit
? nothing
: html`
<ha-list-item
graphic="icon"
@click=${this._handleAction}
.action=${"edit"}
>
<ha-svg-icon slot="graphic" .path=${mdiPencil}></ha-svg-icon>
<ha-dropdown-item value="edit">
<ha-svg-icon slot="icon" .path=${mdiPencil}></ha-svg-icon>
${this.hass.localize(
"ui.panel.lovelace.editor.edit_card.edit"
)}
</ha-list-item>
</ha-dropdown-item>
`}
${this.noDuplicate
? nothing
: html`
<ha-list-item
graphic="icon"
@click=${this._handleAction}
.action=${"duplicate"}
>
<ha-dropdown-item value="duplicate">
<ha-svg-icon
slot="graphic"
slot="icon"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
${this.hass.localize(
"ui.panel.lovelace.editor.edit_card.duplicate"
)}
</ha-list-item>
</ha-dropdown-item>
`}
${this.noMove
? nothing
: html`
<ha-list-item
graphic="icon"
@click=${this._handleAction}
.action=${"copy"}
>
<ha-dropdown-item value="copy">
<ha-svg-icon
slot="graphic"
slot="icon"
.path=${mdiContentCopy}
></ha-svg-icon>
${this.hass.localize(
"ui.panel.lovelace.editor.edit_card.copy"
)}
</ha-list-item>
<ha-list-item
graphic="icon"
@click=${this._handleAction}
.action=${"cut"}
>
<ha-svg-icon
slot="graphic"
.path=${mdiContentCut}
></ha-svg-icon>
</ha-dropdown-item>
<ha-dropdown-item value="cut">
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
${this.hass.localize(
"ui.panel.lovelace.editor.edit_card.cut"
)}
</ha-list-item>
</ha-dropdown-item>
`}
${this.noDuplicate && this.noEdit && this.noMove
? nothing
: html`<li divider role="separator"></li>`}
<ha-list-item
graphic="icon"
class="warning"
@click=${this._handleAction}
.action=${"delete"}
>
: html`<wa-divider></wa-divider>`}
<ha-dropdown-item value="delete" variant="danger">
${this.hass.localize("ui.panel.lovelace.editor.edit_card.delete")}
<ha-svg-icon
class="warning"
slot="graphic"
slot="icon"
.path=${mdiDelete}
></ha-svg-icon>
</ha-list-item>
</ha-button-menu>
</ha-dropdown-item>
</ha-dropdown>
</div>
`;
}
private _handleOpened() {
this._menuOpened = true;
}
private _handleClosed() {
this._menuOpened = false;
}
private _handleOverlayClick(ev): void {
if (ev.defaultPrevented) {
return;
@@ -228,8 +190,14 @@ export class HuiCardEditMode extends LitElement {
this._editCard();
}
private _handleAction(ev) {
switch (ev.currentTarget.action) {
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "edit":
this._editCard();
break;
@@ -330,14 +298,12 @@ export class HuiCardEditMode extends LitElement {
background: var(--secondary-background-color);
--mdc-icon-size: 20px;
}
.more {
.more ha-icon-button {
position: absolute;
right: -6px;
top: -6px;
inset-inline-end: -6px;
inset-inline-start: initial;
}
.more ha-icon-button {
cursor: pointer;
border-radius: var(--ha-border-radius-circle);
background: var(--secondary-background-color);

View File

@@ -1,4 +1,4 @@
import type { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@home-assistant/webawesome/dist/components/divider/divider";
import {
mdiContentCopy,
mdiContentCut,
@@ -15,10 +15,11 @@ import { customElement, property, queryAssignedElements } from "lit/decorators";
import { storage } from "../../../common/decorators/storage";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-button";
import "../../../components/ha-button-menu";
import "../../../components/ha-card";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-icon-button";
import "../../../components/ha-list-item";
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
import { saveConfig } from "../../../data/lovelace/config/types";
import { isStrategyView } from "../../../data/lovelace/config/view";
@@ -132,7 +133,10 @@ export class HuiCardOptions extends LitElement {
></ha-icon-button>
`
: nothing}
<ha-button-menu @action=${this._handleAction}>
<ha-dropdown
@wa-select=${this._handleDropdownSelect}
placement="bottom-end"
>
<ha-icon-button
slot="trigger"
.label=${this.hass!.localize(
@@ -140,52 +144,46 @@ export class HuiCardOptions extends LitElement {
)}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-list-item graphic="icon">
<ha-dropdown-item value="move">
<ha-svg-icon
slot="graphic"
slot="icon"
.path=${mdiFileMoveOutline}
></ha-svg-icon>
${this.hass!.localize(
"ui.panel.lovelace.editor.edit_card.move"
)}
</ha-list-item>
<ha-list-item graphic="icon">
</ha-dropdown-item>
<ha-dropdown-item value="duplicate">
<ha-svg-icon
slot="graphic"
slot="icon"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
${this.hass!.localize(
"ui.panel.lovelace.editor.edit_card.duplicate"
)}
</ha-list-item>
<ha-list-item graphic="icon">
<ha-svg-icon
slot="graphic"
.path=${mdiContentCopy}
></ha-svg-icon>
</ha-dropdown-item>
<ha-dropdown-item value="copy">
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
${this.hass!.localize(
"ui.panel.lovelace.editor.edit_card.copy"
)}
</ha-list-item>
<ha-list-item graphic="icon">
<ha-svg-icon
slot="graphic"
.path=${mdiContentCut}
></ha-svg-icon>
</ha-dropdown-item>
<ha-dropdown-item value="cut">
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
${this.hass!.localize("ui.panel.lovelace.editor.edit_card.cut")}
</ha-list-item>
<li divider role="separator"></li>
<ha-list-item class="warning" graphic="icon">
</ha-dropdown-item>
<wa-divider></wa-divider>
<ha-dropdown-item value="delete" variant="danger">
<ha-svg-icon
class="warning"
slot="graphic"
slot="icon"
.path=${mdiDelete}
></ha-svg-icon>
${this.hass!.localize(
"ui.panel.lovelace.editor.edit_card.delete"
)}
</ha-list-item>
</ha-button-menu>
</ha-dropdown-item>
</ha-dropdown>
</div>
</div>
</ha-card>
@@ -244,30 +242,31 @@ export class HuiCardOptions extends LitElement {
ha-icon-button.move-arrow[disabled] {
color: var(--disabled-text-color);
}
ha-list-item {
cursor: pointer;
white-space: nowrap;
}
`,
];
}
private _handleAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "move":
this._moveCard();
break;
case 1:
case "duplicate":
this._duplicateCard();
break;
case 2:
case "copy":
this._copyCard();
break;
case 3:
case "cut":
this._cutCard();
break;
case 4:
case "delete":
this._deleteCard();
break;
}

View File

@@ -1,3 +1,4 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import { ResizeController } from "@lit-labs/observers/resize-controller";
import {
mdiChevronDown,
@@ -23,6 +24,10 @@ import {
extractSearchParam,
} from "../../common/url/search-params";
import "../../components/ha-button";
import "../../components/ha-button-menu";
import "../../components/ha-dropdown";
import "../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../components/ha-dropdown-item";
import "../../components/ha-fab";
import "../../components/ha-icon-button";
import "../../components/ha-list";
@@ -222,47 +227,41 @@ class PanelTodo extends LitElement {
${this.hass.localize("ui.panel.todo.create_list")}
</ha-list-item>`
: nothing}
<ha-button-menu slot="actionItems">
<ha-dropdown
slot="actionItems"
@wa-select=${this._handleDropdownSelect}
>
<ha-icon-button
slot="trigger"
.label=${""}
.path=${mdiDotsVertical}
></ha-icon-button>
${this._conversation(this.hass.config.components)
? html`<ha-list-item
graphic="icon"
@click=${this._showMoreInfoDialog}
.disabled=${!this._entityId}
>
<ha-svg-icon .path=${mdiInformationOutline} slot="graphic">
? html`<ha-dropdown-item value="info" .disabled=${!this._entityId}>
<ha-svg-icon .path=${mdiInformationOutline} slot="icon">
</ha-svg-icon>
${this.hass.localize("ui.panel.todo.information")}
</ha-list-item>`
</ha-dropdown-item>`
: nothing}
<li divider role="separator"></li>
<ha-list-item graphic="icon" @click=${this._showVoiceCommandDialog}>
<ha-svg-icon .path=${mdiCommentProcessingOutline} slot="graphic">
<wa-divider></wa-divider>
<ha-dropdown-item value="assist">
<ha-svg-icon .path=${mdiCommentProcessingOutline} slot="icon">
</ha-svg-icon>
${this.hass.localize("ui.panel.todo.assist")}
</ha-list-item>
</ha-dropdown-item>
${entityRegistryEntry?.platform === "local_todo"
? html` <li divider role="separator"></li>
<ha-list-item
graphic="icon"
@click=${this._deleteList}
class="warning"
? html` <wa-divider></wa-divider>
<ha-dropdown-item
value="delete"
variant="danger"
.disabled=${!this._entityId}
>
<ha-svg-icon
.path=${mdiDelete}
slot="graphic"
class="warning"
>
<ha-svg-icon .path=${mdiDelete} slot="icon" class="warning">
</ha-svg-icon>
${this.hass.localize("ui.panel.todo.delete_list")}
</ha-list-item>`
</ha-dropdown-item>`
: nothing}
</ha-button-menu>
</ha-dropdown>
<div id="columns">
<div class="column">
${this._entityId
@@ -363,6 +362,26 @@ class PanelTodo extends LitElement {
showTodoItemEditDialog(this, { entity: this._entityId! });
}
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "info":
this._showMoreInfoDialog();
break;
case "assist":
this._showVoiceCommandDialog();
break;
case "delete":
this._deleteList();
break;
}
}
static get styles(): CSSResultGroup {
return [
haStyle,

View File

@@ -658,7 +658,8 @@
"show_entities": "Show entities",
"new_entity": "Create a new entity",
"placeholder": "Select an entity",
"create_helper": "Create a new {domain, select, \n undefined {} \n other {{domain} }\n } helper."
"create_helper": "Create a new {domain, select, \n undefined {} \n other {{domain} }\n } helper.",
"unknown": "Unknown entity selected"
},
"entity-name-picker": {
"types": {
@@ -779,7 +780,8 @@
"user-picker": {
"no_match": "No users found for {term}",
"user": "User",
"add_user": "Add user"
"add_user": "Add user",
"unknown": "Unknown user selected"
},
"blueprint-picker": {
"select_blueprint": "Select a blueprint"
@@ -793,7 +795,8 @@
"device": "Device",
"unnamed_device": "Unnamed device",
"no_area": "No area",
"placeholder": "Select a device"
"placeholder": "Select a device",
"unknown": "Unknown device selected"
},
"category-picker": {
"clear": "Clear",
@@ -805,6 +808,7 @@
"add_new": "Add new category…",
"no_categories": "No categories available",
"no_match": "No categories found for {term}",
"unknown": "Unknown category selected",
"add_dialog": {
"title": "Add new category",
"text": "Enter the name of the new category.",
@@ -831,7 +835,8 @@
"add_new": "Add new area…",
"no_areas": "No areas available",
"no_match": "No areas found for {term}",
"failed_create_area": "Failed to create area."
"failed_create_area": "Failed to create area.",
"unknown": "Unknown area selected"
},
"floor-picker": {
"clear": "Clear",
@@ -841,7 +846,8 @@
"add_new": "Add new floor…",
"no_floors": "No floors available",
"no_match": "No floors found for {term}",
"failed_create_floor": "Failed to create floor."
"failed_create_floor": "Failed to create floor.",
"unknown": "Unknown floor selected"
},
"area-filter": {
"title": "Areas",
@@ -858,7 +864,8 @@
"no_match": "No statistics found for {term}",
"no_state": "Entity without state",
"missing_entity": "Why is my entity not listed?",
"learn_more": "Learn more about statistics"
"learn_more": "Learn more about statistics",
"unknown": "Unknown statistic selected"
},
"addon-picker": {
"addon": "Add-on",
@@ -998,7 +1005,8 @@
},
"service-picker": {
"action": "Action",
"no_match": "No matching actions found"
"no_match": "No matching actions found",
"unknown": "Unknown action selected"
},
"service-control": {
"required": "This field is required",
@@ -1296,7 +1304,8 @@
},
"combo-box": {
"no_match": "No matching items found",
"no_items": "No items available"
"no_items": "No items available",
"unknown_item": "Unknown item"
},
"suggest_with_ai": {
"label": "Suggest",
@@ -2475,10 +2484,11 @@
"area_reorder_failed": "Failed to reorder areas",
"area_move_failed": "Failed to move area",
"floor_reorder_failed": "Failed to reorder floors",
"reorder": "Reorder floors and areas"
"reorder": "Reorder"
},
"dialog": {
"reorder_title": "Reorder floors and areas",
"reorder_areas_title": "Reorder areas",
"reorder_floors_areas_title": "Reorder floors and areas",
"other_areas": "Other areas",
"reorder_failed": "Failed to save order",
"empty_floor": "No areas on this floor",
@@ -3205,6 +3215,9 @@
"cost_stat_input": "[%key:ui::panel::config::energy::grid::flow_dialog::from::cost_stat_input%]",
"cost_entity": "[%key:ui::panel::config::energy::grid::flow_dialog::from::cost_entity%]",
"cost_entity_input": "[%key:ui::panel::config::energy::grid::flow_dialog::from::cost_entity_input%]",
"cost_entity_helper": "Any entity with a unit of `{currency}/(valid {class} unit)` (e.g. `{currency}/{unit1}` or `{currency}/{unit2}`) may be used and will be automatically converted.",
"cost_entity_helper_energy": "energy",
"cost_entity_helper_volume": "volume",
"cost_number": "[%key:ui::panel::config::energy::grid::flow_dialog::from::cost_number%]",
"cost_number_input": "[%key:ui::panel::config::energy::grid::flow_dialog::from::cost_number%]",
"gas_usage": "Gas usage"
@@ -3228,6 +3241,7 @@
"cost_stat_input": "[%key:ui::panel::config::energy::grid::flow_dialog::from::cost_stat_input%]",
"cost_entity": "[%key:ui::panel::config::energy::grid::flow_dialog::from::cost_entity%]",
"cost_entity_input": "[%key:ui::panel::config::energy::grid::flow_dialog::from::cost_entity_input%]",
"cost_entity_helper": "Any entity with a unit of `{currency}/(valid water unit)` (e.g. `{currency}/gal` or `{currency}/m³`) may be used and will be automatically converted.",
"cost_number": "[%key:ui::panel::config::energy::grid::flow_dialog::from::cost_number%]",
"cost_number_input": "[%key:ui::panel::config::energy::grid::flow_dialog::from::cost_number%]",
"water_usage": "Water usage"

View File

@@ -3893,22 +3893,22 @@ __metadata:
languageName: node
linkType: hard
"@rsdoctor/client@npm:1.3.11":
version: 1.3.11
resolution: "@rsdoctor/client@npm:1.3.11"
checksum: 10/17b769e8b6ae23e508816be05fb5b9ab235bd4380f2bc3ff7a8add8ae088eee0663627a637d695f304841fb708f19b245ab20a4924f3b0b9f35e341649028490
"@rsdoctor/client@npm:1.3.12":
version: 1.3.12
resolution: "@rsdoctor/client@npm:1.3.12"
checksum: 10/3abd14af2bd9a34da2199bad3a9c9aef34381beebebf3d0aceb90e4e9dc9ea19e804372bcdb8e082888dedb268fe544b658537c510df7ae9160df822ced9712f
languageName: node
linkType: hard
"@rsdoctor/core@npm:1.3.11":
version: 1.3.11
resolution: "@rsdoctor/core@npm:1.3.11"
"@rsdoctor/core@npm:1.3.12":
version: 1.3.12
resolution: "@rsdoctor/core@npm:1.3.12"
dependencies:
"@rsbuild/plugin-check-syntax": "npm:1.5.0"
"@rsdoctor/graph": "npm:1.3.11"
"@rsdoctor/sdk": "npm:1.3.11"
"@rsdoctor/types": "npm:1.3.11"
"@rsdoctor/utils": "npm:1.3.11"
"@rsdoctor/graph": "npm:1.3.12"
"@rsdoctor/sdk": "npm:1.3.12"
"@rsdoctor/types": "npm:1.3.12"
"@rsdoctor/utils": "npm:1.3.12"
browserslist-load-config: "npm:^1.0.1"
enhanced-resolve: "npm:5.12.0"
es-toolkit: "npm:^1.41.0"
@@ -3916,59 +3916,59 @@ __metadata:
fs-extra: "npm:^11.1.1"
semver: "npm:^7.7.3"
source-map: "npm:^0.7.6"
checksum: 10/1e11f76f00ef4148743c5b5ef8dcefdc57f658ffefcf6de6455888b4546000ebf6a9d03623734bbb7e3b5cd619bb460c668c38e6ffa0f747890018bbc3fc005a
checksum: 10/fda3d1a0acb0e57888a5bbe28649c1b439951e915d6411982116bdaabb72185d4f339009fd748681fe1c9c341c3c46fad597b587d8c3273b2024d682f6b40aca
languageName: node
linkType: hard
"@rsdoctor/graph@npm:1.3.11":
version: 1.3.11
resolution: "@rsdoctor/graph@npm:1.3.11"
"@rsdoctor/graph@npm:1.3.12":
version: 1.3.12
resolution: "@rsdoctor/graph@npm:1.3.12"
dependencies:
"@rsdoctor/types": "npm:1.3.11"
"@rsdoctor/utils": "npm:1.3.11"
"@rsdoctor/types": "npm:1.3.12"
"@rsdoctor/utils": "npm:1.3.12"
es-toolkit: "npm:^1.41.0"
path-browserify: "npm:1.0.1"
source-map: "npm:^0.7.6"
checksum: 10/e32444685f98cad184eb9d07bc9ebd4a3668c6dfaa352ce6912cabc4cdc63bdf15efc57a3d145b35e14724215230bd6657b7b8de30aa2c674a64c2c7983a3739
checksum: 10/38b07882c0e90fc9a97c68ee571f7b649c454624d403e8745ef85b6c2ad2c698f922aa1fb313a23cc1ceadfe391f48b23d381f73cf089ab5762a14a095dd26f3
languageName: node
linkType: hard
"@rsdoctor/rspack-plugin@npm:1.3.11":
version: 1.3.11
resolution: "@rsdoctor/rspack-plugin@npm:1.3.11"
"@rsdoctor/rspack-plugin@npm:1.3.12":
version: 1.3.12
resolution: "@rsdoctor/rspack-plugin@npm:1.3.12"
dependencies:
"@rsdoctor/core": "npm:1.3.11"
"@rsdoctor/graph": "npm:1.3.11"
"@rsdoctor/sdk": "npm:1.3.11"
"@rsdoctor/types": "npm:1.3.11"
"@rsdoctor/utils": "npm:1.3.11"
"@rsdoctor/core": "npm:1.3.12"
"@rsdoctor/graph": "npm:1.3.12"
"@rsdoctor/sdk": "npm:1.3.12"
"@rsdoctor/types": "npm:1.3.12"
"@rsdoctor/utils": "npm:1.3.12"
peerDependencies:
"@rspack/core": "*"
peerDependenciesMeta:
"@rspack/core":
optional: true
checksum: 10/973626b9387d85814839c7a8f08b21b60da079c4f755dd5ada5fae8aba61910d27ead333c35d3f9154ba71253c420bed43481bd68c09dae618d42f47a6d8c080
checksum: 10/2825034e62d89d9e9eeb5d4fa68ab3b19a12ee6b270fb103fd694e649dcaccfe08016879271c5d2cd2ee961179df43e9b124f965d2933b24dfaadc98441c2cc3
languageName: node
linkType: hard
"@rsdoctor/sdk@npm:1.3.11":
version: 1.3.11
resolution: "@rsdoctor/sdk@npm:1.3.11"
"@rsdoctor/sdk@npm:1.3.12":
version: 1.3.12
resolution: "@rsdoctor/sdk@npm:1.3.12"
dependencies:
"@rsdoctor/client": "npm:1.3.11"
"@rsdoctor/graph": "npm:1.3.11"
"@rsdoctor/types": "npm:1.3.11"
"@rsdoctor/utils": "npm:1.3.11"
"@rsdoctor/client": "npm:1.3.12"
"@rsdoctor/graph": "npm:1.3.12"
"@rsdoctor/types": "npm:1.3.12"
"@rsdoctor/utils": "npm:1.3.12"
safer-buffer: "npm:2.1.2"
socket.io: "npm:4.8.1"
tapable: "npm:2.2.3"
checksum: 10/6e98b51259178af26cad8852a2ac4f35a404a34978d60262f6100320b02c47fdcd11df2080c384d765eca28edfed65c8f0e1b49bf6cdac462d5c0cff0ec599f9
checksum: 10/5b60197500b73b1ae671565a2c2886a40df36fcf0e7277457840f569cb845d2c725fbfe43a4f6496ac5a26d749e0249e1a2a79bdb582a0fe16beeaa9bea0cdaa
languageName: node
linkType: hard
"@rsdoctor/types@npm:1.3.11":
version: 1.3.11
resolution: "@rsdoctor/types@npm:1.3.11"
"@rsdoctor/types@npm:1.3.12":
version: 1.3.12
resolution: "@rsdoctor/types@npm:1.3.12"
dependencies:
"@types/connect": "npm:3.4.38"
"@types/estree": "npm:1.0.5"
@@ -3982,16 +3982,16 @@ __metadata:
optional: true
webpack:
optional: true
checksum: 10/383cc2182737d6a9ab49a43f826bc16c5a2688f1135fe44586b835153c24fcae1bac5ea16683d252df91ec269608aa8e8926c07b0ed17f3d954e7a17d30a4650
checksum: 10/1d8891362056332289dfefd22a9ab0daf977ef8d82cffa1bc2abf507423ddf628242188b826bcf34e8fd908bb1b22f093c840675e77593a7f93e671e91261c5f
languageName: node
linkType: hard
"@rsdoctor/utils@npm:1.3.11":
version: 1.3.11
resolution: "@rsdoctor/utils@npm:1.3.11"
"@rsdoctor/utils@npm:1.3.12":
version: 1.3.12
resolution: "@rsdoctor/utils@npm:1.3.12"
dependencies:
"@babel/code-frame": "npm:7.26.2"
"@rsdoctor/types": "npm:1.3.11"
"@rsdoctor/types": "npm:1.3.12"
"@types/estree": "npm:1.0.5"
acorn: "npm:^8.10.0"
acorn-import-attributes: "npm:^1.9.5"
@@ -4005,7 +4005,7 @@ __metadata:
picocolors: "npm:^1.1.1"
rslog: "npm:^1.2.11"
strip-ansi: "npm:^6.0.1"
checksum: 10/f5d1bc0dbfc39753adbe2ee89d524a97d7568c6e1ad8059e479993fdb79e4b753e6cfb714bf641e5f336071215cb76c9b965a2ea004944d6c137e60d8285a1e7
checksum: 10/24d46e84abf2f85514f9feabb44db9b7a1e9e161052c8cdb8edc561db797928fd0d257249e1fb228f2b9346dc446e0ec59e03d46307f5b1e4bf5554a73274ee6
languageName: node
linkType: hard
@@ -9250,7 +9250,7 @@ __metadata:
"@octokit/plugin-retry": "npm:8.0.3"
"@octokit/rest": "npm:22.0.1"
"@replit/codemirror-indentation-markers": "npm:6.5.3"
"@rsdoctor/rspack-plugin": "npm:1.3.11"
"@rsdoctor/rspack-plugin": "npm:1.3.12"
"@rspack/core": "npm:1.6.5"
"@rspack/dev-server": "npm:1.1.4"
"@swc/helpers": "npm:0.5.17"