Automation editor: overflow changes and style fixes (#26744)

* Fix for width also for blueprint editor

* Fix overflow menus

* Fix option icons

* Fix iOS bottom sheet flickering and drag handle

* Fix mobile padding

* Fix padding in sidebar

* Fix overflow placement

* Add new a11y sort

* Remove overflow in rows

* Fix a11y select row

* Revert "Fix a11y select row"

This reverts commit 54260c4a37.

* Fix option padding on blueprint

---------

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
This commit is contained in:
Wendelin
2025-08-28 18:42:45 +02:00
committed by GitHub
parent cf8d36b1f3
commit 25f25243bd
20 changed files with 783 additions and 474 deletions

View File

@@ -1,7 +1,7 @@
import { mdiChevronUp } from "@mdi/js";
import type { TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { customElement, property, query } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import "./ha-icon-button";
@@ -16,12 +16,18 @@ export class HaAutomationRow extends LitElement {
@property({ type: Boolean, reflect: true })
public selected = false;
@property({ type: Boolean, reflect: true, attribute: "sort-selected" })
public sortSelected = false;
@property({ type: Boolean, reflect: true })
public disabled = false;
@property({ type: Boolean, reflect: true, attribute: "building-block" })
public buildingBlock = false;
@query(".row")
private _rowElement?: HTMLDivElement;
protected render(): TemplateResult {
return html`
<div
@@ -66,15 +72,40 @@ export class HaAutomationRow extends LitElement {
if (ev.defaultPrevented) {
return;
}
if (ev.key !== "Enter" && ev.key !== " ") {
if (
ev.key !== "Enter" &&
ev.key !== " " &&
!(
(this.sortSelected || ev.altKey) &&
(ev.key === "ArrowUp" || ev.key === "ArrowDown")
)
) {
return;
}
ev.preventDefault();
ev.stopPropagation();
if (ev.key === "ArrowUp" || ev.key === "ArrowDown") {
if (ev.key === "ArrowUp") {
fireEvent(this, "move-up");
return;
}
fireEvent(this, "move-down");
return;
}
if (this.sortSelected && (ev.key === "Enter" || ev.key === " ")) {
fireEvent(this, "stop-sort-selection");
return;
}
this.click();
}
public focus() {
requestAnimationFrame(() => this._rowElement?.focus());
}
static styles = css`
:host {
display: block;
@@ -134,6 +165,11 @@ export class HaAutomationRow extends LitElement {
overflow-wrap: anywhere;
margin: 0 12px;
}
:host([sort-selected]) .row {
box-shadow:
0px 0px 8px 4px rgba(var(--rgb-accent-color), 0.8),
inset 0px 2px 8px 4px rgba(var(--rgb-accent-color), 0.4);
}
`;
}
@@ -144,5 +180,6 @@ declare global {
interface HASSDomEvents {
"toggle-collapsed": undefined;
"stop-sort-selection": undefined;
}
}

View File

@@ -197,6 +197,7 @@ export class HaBottomSheet extends LitElement {
justify-content: center;
align-items: center;
z-index: 7;
padding-bottom: 76px;
}
.handle-wrapper .handle::after {
content: "";

View File

@@ -16,9 +16,23 @@ export class HaMdButtonMenu extends LitElement {
@property() public positioning?: "fixed" | "absolute" | "popover";
@property({ attribute: "anchor-corner" }) public anchorCorner:
| "start-start"
| "start-end"
| "end-start"
| "end-end" = "end-start";
@property({ attribute: "menu-corner" }) public menuCorner:
| "start-start"
| "start-end"
| "end-start"
| "end-end" = "start-start";
@property({ type: Boolean, attribute: "has-overflow" }) public hasOverflow =
false;
@property({ type: Boolean }) public quick = false;
@query("ha-md-menu", true) private _menu!: HaMdMenu;
public get items() {
@@ -39,8 +53,11 @@ export class HaMdButtonMenu extends LitElement {
<slot name="trigger" @slotchange=${this._setTriggerAria}></slot>
</div>
<ha-md-menu
.quick=${this.quick}
.positioning=${this.positioning}
.hasOverflow=${this.hasOverflow}
.anchorCorner=${this.anchorCorner}
.menuCorner=${this.menuCorner}
@opening=${this._handleOpening}
@closing=${this._handleClosing}
>

View File

@@ -53,6 +53,7 @@ export default class HaAutomationActionEditor extends LitElement {
this.disabled || (this.action.enabled === false && !this.yamlMode),
yaml: yamlMode,
indent: this.indent,
card: !this.inSidebar,
})}
>
${yamlMode

View File

@@ -26,6 +26,7 @@ import { stopPropagation } from "../../../../common/dom/stop_propagation";
import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter";
import { handleStructError } from "../../../../common/structs/handle-errors";
import "../../../../components/ha-automation-row";
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
import "../../../../components/ha-card";
import "../../../../components/ha-expansion-panel";
import "../../../../components/ha-icon-button";
@@ -153,6 +154,9 @@ export default class HaAutomationActionRow extends LitElement {
@property({ type: Boolean, attribute: "sidebar" })
public optionsInSidebar = false;
@property({ type: Boolean, attribute: "sort-selected" })
public sortSelected = false;
@storage({
key: "automationClipboard",
state: false,
@@ -186,6 +190,9 @@ export default class HaAutomationActionRow extends LitElement {
@query("ha-automation-action-editor")
private _actionEditor?: HaAutomationActionEditor;
@query("ha-automation-row")
private _automationRowElement?: HaAutomationRow;
protected firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties);
@@ -254,13 +261,16 @@ export default class HaAutomationActionRow extends LitElement {
<ha-svg-icon .path=${mdiAlertCircleCheck}></ha-svg-icon>
</ha-tooltip>`
: nothing}
<ha-md-button-menu
${!this.optionsInSidebar
? html`<ha-md-button-menu
quick
slot="icons"
@click=${preventDefaultStopPropagation}
@keydown=${stopPropagation}
@closed=${stopPropagation}
positioning="fixed"
anchor-corner="end-end"
menu-corner="start-end"
>
<ha-icon-button
slot="trigger"
@@ -268,8 +278,7 @@ export default class HaAutomationActionRow extends LitElement {
.path=${mdiDotsVertical}
></ha-icon-button>
${!this.optionsInSidebar
? html`<ha-md-menu-item .clickAction=${this._runAction}>
<ha-md-menu-item .clickAction=${this._runAction}>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.run"
)}
@@ -316,8 +325,7 @@ export default class HaAutomationActionRow extends LitElement {
"ui.panel.config.automation.editor.triggers.cut"
)}
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
</ha-md-menu-item>`
: nothing}
</ha-md-menu-item>
<ha-md-menu-item
.clickAction=${this._moveUp}
@@ -331,22 +339,20 @@ export default class HaAutomationActionRow extends LitElement {
.clickAction=${this._moveDown}
.disabled=${this.disabled || !!this.last}
>
${this.hass.localize("ui.panel.config.automation.editor.move_down")}
${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
<ha-svg-icon slot="start" .path=${mdiArrowDown}></ha-svg-icon
></ha-md-menu-item>
${!this.optionsInSidebar
? html`<ha-md-menu-item
<ha-md-menu-item
.clickAction=${this._toggleYamlMode}
.disabled=${!this._uiModeAvailable || !!this._warnings}
>
${this.hass.localize(
`ui.panel.config.automation.editor.edit_${!this._yamlMode ? "yaml" : "ui"}`
)}
<ha-svg-icon
slot="start"
.path=${mdiPlaylistEdit}
></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
</ha-md-menu-item>
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
@@ -382,10 +388,9 @@ export default class HaAutomationActionRow extends LitElement {
slot="start"
.path=${mdiDelete}
></ha-svg-icon>
</ha-md-menu-item>`
</ha-md-menu-item>
</ha-md-button-menu>`
: nothing}
</ha-md-button-menu>
${!this.optionsInSidebar
? html`${this._warnings
? html`<ha-automation-editor-warning
@@ -447,6 +452,7 @@ export default class HaAutomationActionRow extends LitElement {
...ACTION_BUILDING_BLOCKS,
...ACTION_COMBINED_BLOCKS,
].includes(blockType!)}
.sortSelected=${this.sortSelected}
>${this._renderRow()}</ha-automation-row
>`
: html`
@@ -697,10 +703,12 @@ export default class HaAutomationActionRow extends LitElement {
this._collapsed = false;
if (this.narrow) {
requestAnimationFrame(() => {
this.scrollIntoView({
block: "start",
behavior: "smooth",
});
});
}
}
@@ -751,6 +759,10 @@ export default class HaAutomationActionRow extends LitElement {
return this._selected;
}
public focus() {
this._automationRowElement?.focus();
}
static styles = rowStyles;
}

View File

@@ -48,6 +48,8 @@ export default class HaAutomationAction extends LitElement {
@state() private _showReorder = false;
@state() private _rowSortSelected?: number;
@state()
@storage({
key: "automationClipboard",
@@ -94,7 +96,7 @@ export default class HaAutomationAction extends LitElement {
@item-removed=${this._actionRemoved}
@item-cloned=${this._actionCloned}
>
<div class="rows">
<div class="rows ${!this.optionsInSidebar ? "no-sidebar" : ""}">
${repeat(
this.actions,
(action) => this._getKey(action),
@@ -115,10 +117,20 @@ export default class HaAutomationAction extends LitElement {
.hass=${this.hass}
?highlight=${this.highlightedActions?.includes(action)}
.optionsInSidebar=${this.optionsInSidebar}
.sortSelected=${this._rowSortSelected === idx}
@stop-sort-selection=${this._stopSortSelection}
>
${this._showReorder && !this.disabled
? html`
<div class="handle" slot="icons">
<div
tabindex="0"
class="handle ${this._rowSortSelected === idx
? "active"
: ""}"
slot="icons"
@keydown=${this._handleDragKeydown}
.index=${idx}
>
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
</div>
`
@@ -264,18 +276,30 @@ export default class HaAutomationAction extends LitElement {
return this._actionKeys.get(action)!;
}
private _moveUp(ev) {
private async _moveUp(ev) {
ev.stopPropagation();
const index = (ev.target as any).index;
if (!(ev.target as HaAutomationActionRow).first) {
const newIndex = index - 1;
this._move(index, newIndex);
if (this._rowSortSelected === index) {
this._rowSortSelected = newIndex;
}
ev.target.focus();
}
}
private _moveDown(ev) {
private async _moveDown(ev) {
ev.stopPropagation();
const index = (ev.target as any).index;
if (!(ev.target as HaAutomationActionRow).last) {
const newIndex = index + 1;
this._move(index, newIndex);
if (this._rowSortSelected === index) {
this._rowSortSelected = newIndex;
}
ev.target.focus();
}
}
private _move(oldIndex: number, newIndex: number) {
@@ -371,6 +395,20 @@ export default class HaAutomationAction extends LitElement {
});
}
private _handleDragKeydown(ev: KeyboardEvent) {
if (ev.key === "Enter" || ev.key === " ") {
ev.stopPropagation();
this._rowSortSelected =
this._rowSortSelected === undefined
? (ev.target as any).index
: undefined;
}
}
private _stopSortSelection() {
this._rowSortSelected = undefined;
}
static styles = [
automationRowsStyles,
css`

View File

@@ -28,6 +28,8 @@ export default class HaAutomationConditionEditor extends LitElement {
@property({ type: Boolean }) public narrow = false;
@property({ type: Boolean, attribute: "sidebar" }) public inSidebar = false;
@property({ type: Boolean, reflect: true }) public selected = false;
@property({ type: Boolean, attribute: "supported" }) public uiSupported =
@@ -55,6 +57,7 @@ export default class HaAutomationConditionEditor extends LitElement {
(this.condition.enabled === false && !this.yamlMode),
yaml: yamlMode,
indent: this.indent,
card: !this.inSidebar,
})}
>
${yamlMode

View File

@@ -26,6 +26,7 @@ import { stopPropagation } from "../../../../common/dom/stop_propagation";
import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter";
import { handleStructError } from "../../../../common/structs/handle-errors";
import "../../../../components/ha-automation-row";
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
import "../../../../components/ha-card";
import "../../../../components/ha-expansion-panel";
import "../../../../components/ha-icon-button";
@@ -115,6 +116,9 @@ export default class HaAutomationConditionRow extends LitElement {
@property({ type: Boolean }) public narrow = false;
@property({ type: Boolean, attribute: "sort-selected" })
public sortSelected = false;
@state() private _collapsed = true;
@state() private _warnings?: string[];
@@ -145,6 +149,9 @@ export default class HaAutomationConditionRow extends LitElement {
@query("ha-automation-condition-editor")
public conditionEditor?: HaAutomationConditionEditor;
@query("ha-automation-row")
private _automationRowElement?: HaAutomationRow;
private _renderRow() {
return html`
<ha-svg-icon
@@ -160,12 +167,16 @@ export default class HaAutomationConditionRow extends LitElement {
<slot name="icons" slot="icons"></slot>
<ha-md-button-menu
${!this.optionsInSidebar
? html`<ha-md-button-menu
quick
slot="icons"
@click=${preventDefaultStopPropagation}
@keydown=${stopPropagation}
@closed=${stopPropagation}
positioning="fixed"
anchor-corner="end-end"
menu-corner="start-end"
>
<ha-icon-button
slot="trigger"
@@ -174,8 +185,6 @@ export default class HaAutomationConditionRow extends LitElement {
>
</ha-icon-button>
${!this.optionsInSidebar
? html`
<ha-md-menu-item .clickAction=${this._testCondition}>
${this.hass.localize(
"ui.panel.config.automation.editor.conditions.test"
@@ -226,8 +235,6 @@ export default class HaAutomationConditionRow extends LitElement {
)}
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
</ha-md-menu-item>
`
: nothing}
<ha-md-menu-item
.clickAction=${this._moveUp}
@@ -241,12 +248,13 @@ export default class HaAutomationConditionRow extends LitElement {
.clickAction=${this._moveDown}
.disabled=${this.disabled || this.last}
>
${this.hass.localize("ui.panel.config.automation.editor.move_down")}
${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
<ha-svg-icon slot="start" .path=${mdiArrowDown}></ha-svg-icon
></ha-md-menu-item>
${!this.optionsInSidebar
? html`<ha-md-menu-item
<ha-md-menu-item
.clickAction=${this._toggleYamlMode}
.disabled=${this._uiSupported(this.condition.condition) ||
!!this._warnings}
@@ -254,10 +262,7 @@ export default class HaAutomationConditionRow extends LitElement {
${this.hass.localize(
`ui.panel.config.automation.editor.edit_${!this._yamlMode ? "yaml" : "ui"}`
)}
<ha-svg-icon
slot="start"
.path=${mdiPlaylistEdit}
></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
</ha-md-menu-item>
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
@@ -293,10 +298,9 @@ export default class HaAutomationConditionRow extends LitElement {
slot="start"
.path=${mdiDelete}
></ha-svg-icon>
</ha-md-menu-item>`
</ha-md-menu-item>
</ha-md-button-menu>`
: nothing}
</ha-md-button-menu>
${!this.optionsInSidebar
? html`${this._warnings
? html`<ha-automation-editor-warning
@@ -356,6 +360,7 @@ export default class HaAutomationConditionRow extends LitElement {
.buildingBlock=${CONDITION_BUILDING_BLOCKS.includes(
this.condition.condition
)}
.sortSelected=${this.sortSelected}
>${this._renderRow()}</ha-automation-row
>`
: html`
@@ -668,10 +673,12 @@ export default class HaAutomationConditionRow extends LitElement {
this._collapsed = false;
if (this.narrow) {
requestAnimationFrame(() => {
this.scrollIntoView({
block: "start",
behavior: "smooth",
});
});
}
}
@@ -688,6 +695,10 @@ export default class HaAutomationConditionRow extends LitElement {
return this._selected;
}
public focus() {
this._automationRowElement?.focus();
}
static get styles(): CSSResultGroup {
return [
rowStyles,

View File

@@ -46,6 +46,8 @@ export default class HaAutomationCondition extends LitElement {
@state() private _showReorder = false;
@state() private _rowSortSelected?: number;
@state()
@storage({
key: "automationClipboard",
@@ -171,7 +173,7 @@ export default class HaAutomationCondition extends LitElement {
@item-removed=${this._conditionRemoved}
@item-cloned=${this._conditionCloned}
>
<div class="rows">
<div class="rows ${!this.optionsInSidebar ? "no-sidebar" : ""}">
${repeat(
this.conditions.filter((c) => typeof c === "object"),
(condition) => this._getKey(condition),
@@ -193,10 +195,20 @@ export default class HaAutomationCondition extends LitElement {
.hass=${this.hass}
?highlight=${this.highlightedConditions?.includes(cond)}
.optionsInSidebar=${this.optionsInSidebar}
.sortSelected=${this._rowSortSelected === idx}
@stop-sort-selection=${this._stopSortSelection}
>
${this._showReorder && !this.disabled
? html`
<div class="handle" slot="icons">
<div
tabindex="0"
class="handle ${this._rowSortSelected === idx
? "active"
: ""}"
slot="icons"
@keydown=${this._handleDragKeydown}
.index=${idx}
>
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
</div>
`
@@ -285,15 +297,27 @@ export default class HaAutomationCondition extends LitElement {
private _moveUp(ev) {
ev.stopPropagation();
const index = (ev.target as any).index;
if (!(ev.target as HaAutomationConditionRow).first) {
const newIndex = index - 1;
this._move(index, newIndex);
if (this._rowSortSelected === index) {
this._rowSortSelected = newIndex;
}
ev.target.focus();
}
}
private _moveDown(ev) {
ev.stopPropagation();
const index = (ev.target as any).index;
if (!(ev.target as HaAutomationConditionRow).last) {
const newIndex = index + 1;
this._move(index, newIndex);
if (this._rowSortSelected === index) {
this._rowSortSelected = newIndex;
}
ev.target.focus();
}
}
private _move(oldIndex: number, newIndex: number) {
@@ -390,6 +414,20 @@ export default class HaAutomationCondition extends LitElement {
});
}
private _handleDragKeydown(ev: KeyboardEvent) {
if (ev.key === "Enter" || ev.key === " ") {
ev.stopPropagation();
this._rowSortSelected =
this._rowSortSelected === undefined
? (ev.target as any).index
: undefined;
}
}
private _stopSortSelection() {
this._rowSortSelected = undefined;
}
static styles = [
automationRowsStyles,
css`

View File

@@ -17,6 +17,7 @@ import { preventDefaultStopPropagation } from "../../../../common/dom/prevent_de
import { stopPropagation } from "../../../../common/dom/stop_propagation";
import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter";
import "../../../../components/ha-automation-row";
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
import "../../../../components/ha-card";
import "../../../../components/ha-expansion-panel";
import "../../../../components/ha-icon-button";
@@ -63,6 +64,9 @@ export default class HaAutomationOptionRow extends LitElement {
@property({ type: Boolean, attribute: "sidebar" })
public optionsInSidebar = false;
@property({ type: Boolean, attribute: "sort-selected" })
public sortSelected = false;
@state() private _expanded = false;
@state() private _selected = false;
@@ -79,6 +83,9 @@ export default class HaAutomationOptionRow extends LitElement {
@query("ha-automation-action")
private _actionElement?: HaAutomationAction;
@query("ha-automation-row")
private _automationRowElement?: HaAutomationRow;
private _expandedChanged(ev) {
if (ev.currentTarget.id !== "option") {
return;
@@ -123,14 +130,17 @@ export default class HaAutomationOptionRow extends LitElement {
<slot name="icons" slot="icons"></slot>
${this.option
${this.option && !this.optionsInSidebar
? html`
<ha-md-button-menu
quick
slot="icons"
@click=${preventDefaultStopPropagation}
@closed=${stopPropagation}
@keydown=${stopPropagation}
positioning="fixed"
anchor-corner="end-end"
menu-corner="start-end"
>
<ha-icon-button
slot="trigger"
@@ -138,8 +148,6 @@ export default class HaAutomationOptionRow extends LitElement {
.path=${mdiDotsVertical}
></ha-icon-button>
${!this.optionsInSidebar
? html`
<ha-md-menu-item
@click=${this._renameOption}
.disabled=${this.disabled}
@@ -147,10 +155,7 @@ export default class HaAutomationOptionRow extends LitElement {
${this.hass.localize(
"ui.panel.config.automation.editor.actions.rename"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiRenameBox}
></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
</ha-md-menu-item>
<ha-md-menu-item
@@ -161,12 +166,10 @@ export default class HaAutomationOptionRow extends LitElement {
"ui.panel.config.automation.editor.actions.duplicate"
)}
<ha-svg-icon
slot="graphic"
slot="start"
.path=${mdiContentDuplicate}
></ha-svg-icon>
</ha-md-menu-item>
`
: nothing}
<ha-md-menu-item
@click=${this._moveUp}
@@ -175,7 +178,7 @@ export default class HaAutomationOptionRow extends LitElement {
${this.hass.localize(
"ui.panel.config.automation.editor.move_up"
)}
<ha-svg-icon slot="graphic" .path=${mdiArrowUp}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiArrowUp}></ha-svg-icon>
</ha-md-menu-item>
<ha-md-menu-item
@@ -185,11 +188,10 @@ export default class HaAutomationOptionRow extends LitElement {
${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
<ha-svg-icon slot="graphic" .path=${mdiArrowDown}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiArrowDown}></ha-svg-icon>
</ha-md-menu-item>
${!this.optionsInSidebar
? html`<ha-md-menu-item
<ha-md-menu-item
@click=${this._removeOption}
class="warning"
.disabled=${this.disabled}
@@ -199,11 +201,10 @@ export default class HaAutomationOptionRow extends LitElement {
)}
<ha-svg-icon
class="warning"
slot="graphic"
slot="start"
.path=${mdiDelete}
></ha-svg-icon>
</ha-md-menu-item>`
: nothing}
</ha-md-menu-item>
</ha-md-button-menu>
`
: nothing}
@@ -215,6 +216,7 @@ export default class HaAutomationOptionRow extends LitElement {
return html`<div
class=${classMap({
"card-content": true,
card: !this.optionsInSidebar,
indent: this.optionsInSidebar,
selected: this._selected,
hidden: this.optionsInSidebar && this._collapsed,
@@ -271,6 +273,7 @@ export default class HaAutomationOptionRow extends LitElement {
.selected=${this._selected}
@click=${this._toggleSidebar}
@toggle-collapsed=${this._toggleCollapse}
.sortSelected=${this.sortSelected}
>${this._renderRow()}</ha-automation-row
>`
: html`
@@ -398,10 +401,12 @@ export default class HaAutomationOptionRow extends LitElement {
this._collapsed = false;
if (this.narrow) {
requestAnimationFrame(() => {
this.scrollIntoView({
block: "start",
behavior: "smooth",
});
});
}
}
@@ -442,6 +447,10 @@ export default class HaAutomationOptionRow extends LitElement {
return this._selected;
}
public focus() {
this._automationRowElement?.focus();
}
static get styles(): CSSResultGroup {
return [
rowStyles,

View File

@@ -1,7 +1,7 @@
import { mdiDrag, mdiPlus } from "@mdi/js";
import deepClone from "deep-clone-simple";
import type { PropertyValues } from "lit";
import { LitElement, html, nothing } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, queryAll, state } from "lit/decorators";
import { repeat } from "lit/directives/repeat";
import { storage } from "../../../../common/decorators/storage";
@@ -37,6 +37,8 @@ export default class HaAutomationOption extends LitElement {
@state() private _showReorder = false;
@state() private _rowSortSelected?: number;
@state()
@storage({
key: "automationClipboard",
@@ -83,7 +85,7 @@ export default class HaAutomationOption extends LitElement {
@item-removed=${this._optionRemoved}
@item-cloned=${this._optionCloned}
>
<div class="rows">
<div class="rows ${!this.optionsInSidebar ? "no-sidebar" : ""}">
${repeat(
this.options,
(option) => this._getKey(option),
@@ -102,10 +104,20 @@ export default class HaAutomationOption extends LitElement {
@value-changed=${this._optionChanged}
.hass=${this.hass}
.optionsInSidebar=${this.optionsInSidebar}
.sortSelected=${this._rowSortSelected === idx}
@stop-sort-selection=${this._stopSortSelection}
>
${this._showReorder && !this.disabled
? html`
<div class="handle" slot="icons">
<div
tabindex="0"
class="handle ${this._rowSortSelected === idx
? "active"
: ""}"
slot="icons"
@keydown=${this._handleDragKeydown}
.index=${idx}
>
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
</div>
`
@@ -207,15 +219,27 @@ export default class HaAutomationOption extends LitElement {
private _moveUp(ev) {
ev.stopPropagation();
const index = (ev.target as any).index;
if (!(ev.target as HaAutomationOptionRow).first) {
const newIndex = index - 1;
this._move(index, newIndex);
if (this._rowSortSelected === index) {
this._rowSortSelected = newIndex;
}
ev.target.focus();
}
}
private _moveDown(ev) {
ev.stopPropagation();
const index = (ev.target as any).index;
if (!(ev.target as HaAutomationOptionRow).last) {
const newIndex = index + 1;
this._move(index, newIndex);
if (this._rowSortSelected === index) {
this._rowSortSelected = newIndex;
}
ev.target.focus();
}
}
private _move(oldIndex: number, newIndex: number) {
@@ -304,7 +328,28 @@ export default class HaAutomationOption extends LitElement {
fireEvent(this, "show-default-actions");
};
static styles = automationRowsStyles;
private _handleDragKeydown(ev: KeyboardEvent) {
if (ev.key === "Enter" || ev.key === " ") {
ev.stopPropagation();
this._rowSortSelected =
this._rowSortSelected === undefined
? (ev.target as any).index
: undefined;
}
}
private _stopSortSelection() {
this._rowSortSelected = undefined;
}
static styles = [
automationRowsStyles,
css`
:host([root]) .rows {
padding-right: 8px;
}
`,
];
}
declare global {

View File

@@ -67,10 +67,13 @@ export default class HaAutomationSidebarCard extends LitElement {
<slot slot="subtitle" name="subtitle"></slot>
<slot name="overflow-menu" slot="actionItems">
<ha-md-button-menu
quick
@click=${this._openOverflowMenu}
@keydown=${stopPropagation}
@closed=${stopPropagation}
.positioning=${this.narrow ? "absolute" : "fixed"}
anchor-corner="end-end"
menu-corner="start-end"
>
<ha-icon-button
slot="trigger"

View File

@@ -182,6 +182,7 @@ export default class HaAutomationSidebarCondition extends LitElement {
@value-changed=${this._valueChangedSidebar}
.disabled=${this.disabled}
@ui-mode-not-available=${this._handleUiModeNotAvailable}
sidebar
></ha-automation-condition-editor> `}
</ha-automation-sidebar-card>`;
}

View File

@@ -67,7 +67,7 @@ export default class HaAutomationSidebarOption extends LitElement {
"ui.panel.config.automation.editor.actions.duplicate"
)}
<ha-svg-icon
slot="graphic"
slot="start"
.path=${mdiContentDuplicate}
></ha-svg-icon>
</ha-md-menu-item>

View File

@@ -193,6 +193,7 @@ export default class HaAutomationSidebarTrigger extends LitElement {
.yamlMode=${this.yamlMode}
.disabled=${this.disabled}
@ui-mode-not-available=${this._handleUiModeNotAvailable}
sidebar
></ha-automation-trigger-editor>
</ha-automation-sidebar-card>
`;

View File

@@ -54,7 +54,7 @@ export const editorStyles = css`
pointer-events: none;
}
.card-content {
.card-content.card {
padding: 16px;
}
.card-content.yaml {
@@ -69,7 +69,7 @@ export const indentStyle = css`
.selector-row,
:host([indent]) ha-form {
margin-left: 12px;
padding: 12px 24px 16px 16px;
padding: 12px 20px 16px 16px;
border-left: 2px solid var(--ha-color-border-neutral-quiet);
border-bottom: 2px solid var(--ha-color-border-neutral-quiet);
border-radius: 0;
@@ -168,7 +168,6 @@ export const manualEditorStyles = css`
@media all and (max-width: 870px) {
.split-view {
gap: 0;
margin-right: -8px;
}
.sidebar {
height: 0;
@@ -197,6 +196,9 @@ export const automationRowsStyles = css`
flex-direction: column;
gap: 16px;
}
.rows.no-sidebar {
margin-right: 0;
}
.sortable-ghost {
background: none;
border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg));
@@ -209,9 +211,19 @@ export const automationRowsStyles = css`
scroll-margin-top: 48px;
}
.handle {
padding: 12px;
margin: 4px;
padding: 8px;
cursor: move; /* fallback if grab cursor is unsupported */
cursor: grab;
border-radius: var(--ha-border-radius-pill);
}
.handle:focus {
outline: var(--wa-focus-ring);
background: var(--ha-color-fill-neutral-quiet-resting);
}
.handle.active {
outline: var(--wa-focus-ring);
background: var(--ha-color-fill-neutral-normal-active);
}
.handle ha-svg-icon {
pointer-events: none;

View File

@@ -29,6 +29,8 @@ export default class HaAutomationTriggerEditor extends LitElement {
@property({ type: Boolean, attribute: "show-id" }) public showId = false;
@property({ type: Boolean, attribute: "sidebar" }) public inSidebar = false;
@query("ha-yaml-editor") public yamlEditor?: HaYamlEditor;
protected render() {
@@ -47,6 +49,7 @@ export default class HaAutomationTriggerEditor extends LitElement {
this.trigger.enabled === false &&
!this.yamlMode),
yaml: yamlMode,
card: !this.inSidebar,
})}
>
${yamlMode

View File

@@ -28,6 +28,7 @@ import { handleStructError } from "../../../../common/structs/handle-errors";
import { debounce } from "../../../../common/util/debounce";
import "../../../../components/ha-alert";
import "../../../../components/ha-automation-row";
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
import "../../../../components/ha-card";
import "../../../../components/ha-expansion-panel";
import "../../../../components/ha-icon-button";
@@ -115,6 +116,9 @@ export default class HaAutomationTriggerRow extends LitElement {
@property({ type: Boolean, attribute: "sidebar" })
public optionsInSidebar = false;
@property({ type: Boolean, attribute: "sort-selected" })
public sortSelected = false;
@state() private _yamlMode = false;
@state() private _triggered?: Record<string, unknown>;
@@ -132,6 +136,9 @@ export default class HaAutomationTriggerRow extends LitElement {
@query("ha-automation-trigger-editor")
public triggerEditor?: HaAutomationTriggerEditor;
@query("ha-automation-row")
private _automationRowElement?: HaAutomationRow;
@storage({
key: "automationClipboard",
state: false,
@@ -165,21 +172,23 @@ export default class HaAutomationTriggerRow extends LitElement {
<slot name="icons" slot="icons"></slot>
<ha-md-button-menu
${!this.optionsInSidebar
? html`<ha-md-button-menu
quick
slot="icons"
@click=${preventDefaultStopPropagation}
@keydown=${stopPropagation}
@closed=${stopPropagation}
positioning="fixed"
anchor-corner="end-end"
menu-corner="start-end"
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
${!this.optionsInSidebar
? html`<ha-md-menu-item
<ha-md-menu-item
.clickAction=${this._renameTrigger}
.disabled=${this.disabled || type === "list"}
>
@@ -232,8 +241,7 @@ export default class HaAutomationTriggerRow extends LitElement {
"ui.panel.config.automation.editor.triggers.cut"
)}
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
</ha-md-menu-item>`
: nothing}
</ha-md-menu-item>
<ha-md-menu-item
.clickAction=${this._moveUp}
@@ -247,12 +255,12 @@ export default class HaAutomationTriggerRow extends LitElement {
.clickAction=${this._moveDown}
.disabled=${this.disabled || this.last}
>
${this.hass.localize("ui.panel.config.automation.editor.move_down")}
${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
<ha-svg-icon slot="start" .path=${mdiArrowDown}></ha-svg-icon
></ha-md-menu-item>
${!this.optionsInSidebar
? html`
<ha-md-menu-item
.clickAction=${this._toggleYamlMode}
.disabled=${!supported || !!this._warnings}
@@ -260,10 +268,7 @@ export default class HaAutomationTriggerRow extends LitElement {
${this.hass.localize(
`ui.panel.config.automation.editor.edit_${!yamlMode ? "yaml" : "ui"}`
)}
<ha-svg-icon
slot="start"
.path=${mdiPlaylistEdit}
></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
</ha-md-menu-item>
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
@@ -301,9 +306,8 @@ export default class HaAutomationTriggerRow extends LitElement {
.path=${mdiDelete}
></ha-svg-icon>
</ha-md-menu-item>
`
</ha-md-button-menu>`
: nothing}
</ha-md-button-menu>
${!this.optionsInSidebar
? html`${this._warnings
? html`<ha-automation-editor-warning
@@ -345,6 +349,7 @@ export default class HaAutomationTriggerRow extends LitElement {
this.trigger.enabled === false}
@click=${this._toggleSidebar}
.selected=${this._selected}
.sortSelected=${this.sortSelected}
>${this._selected
? "selected"
: nothing}${this._renderRow()}</ha-automation-row
@@ -500,10 +505,12 @@ export default class HaAutomationTriggerRow extends LitElement {
this._selected = true;
if (this.narrow) {
requestAnimationFrame(() => {
this.scrollIntoView({
block: "start",
behavior: "smooth",
});
});
}
}
@@ -670,6 +677,10 @@ export default class HaAutomationTriggerRow extends LitElement {
return this._selected;
}
public focus() {
this._automationRowElement?.focus();
}
static get styles(): CSSResultGroup {
return [
rowStyles,

View File

@@ -47,6 +47,8 @@ export default class HaAutomationTrigger extends LitElement {
@state() private _showReorder = false;
@state() private _rowSortSelected?: number;
@state()
@storage({
key: "automationClipboard",
@@ -90,7 +92,7 @@ export default class HaAutomationTrigger extends LitElement {
@item-removed=${this._triggerRemoved}
@item-cloned=${this._triggerCloned}
>
<div class="rows">
<div class="rows ${!this.optionsInSidebar ? "no-sidebar" : ""}">
${repeat(
this.triggers,
(trigger) => this._getKey(trigger),
@@ -110,10 +112,20 @@ export default class HaAutomationTrigger extends LitElement {
.narrow=${this.narrow}
?highlight=${this.highlightedTriggers?.includes(trg)}
.optionsInSidebar=${this.optionsInSidebar}
.sortSelected=${this._rowSortSelected === idx}
@stop-sort-selection=${this._stopSortSelection}
>
${this._showReorder && !this.disabled
? html`
<div class="handle" slot="icons">
<div
tabindex="0"
class="handle ${this._rowSortSelected === idx
? "active"
: ""}"
slot="icons"
@keydown=${this._handleDragKeydown}
.index=${idx}
>
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
</div>
`
@@ -226,15 +238,27 @@ export default class HaAutomationTrigger extends LitElement {
private _moveUp(ev) {
ev.stopPropagation();
const index = (ev.target as any).index;
if (!(ev.target as HaAutomationTriggerRow).first) {
const newIndex = index - 1;
this._move(index, newIndex);
if (this._rowSortSelected === index) {
this._rowSortSelected = newIndex;
}
ev.target.focus();
}
}
private _moveDown(ev) {
ev.stopPropagation();
const index = (ev.target as any).index;
if (!(ev.target as HaAutomationTriggerRow).last) {
const newIndex = index + 1;
this._move(index, newIndex);
if (this._rowSortSelected === index) {
this._rowSortSelected = newIndex;
}
ev.target.focus();
}
}
private _move(oldIndex: number, newIndex: number) {
@@ -330,6 +354,20 @@ export default class HaAutomationTrigger extends LitElement {
});
}
private _handleDragKeydown(ev: KeyboardEvent) {
if (ev.key === "Enter" || ev.key === " ") {
ev.stopPropagation();
this._rowSortSelected =
this._rowSortSelected === undefined
? (ev.target as any).index
: undefined;
}
}
private _stopSortSelection() {
this._rowSortSelected = undefined;
}
static styles = [
automationRowsStyles,
css`

View File

@@ -1,8 +1,11 @@
import { mdiDelete } from "@mdi/js";
import type { CSSResultGroup } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../../../common/dom/fire_event";
import { preventDefaultStopPropagation } from "../../../common/dom/prevent_default_stop_propagation";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import type { LocalizeKeys } from "../../../common/translations/localize";
import "../../../components/ha-automation-row";
import "../../../components/ha-card";
@@ -61,6 +64,29 @@ export default class HaScriptFieldRow extends LitElement {
<h3 slot="header">${this.key}</h3>
<slot name="icons" slot="icons"></slot>
<ha-md-button-menu
quick
slot="icons"
@click=${preventDefaultStopPropagation}
@keydown=${stopPropagation}
@closed=${stopPropagation}
positioning="fixed"
anchor-corner="end-end"
menu-corner="start-end"
>
<ha-md-menu-item
slot="menu-items"
.clickAction=${this._onDelete}
.disabled=${this.disabled}
class="warning"
>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
)}
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
</ha-md-menu-item>
</ha-md-button-menu>
</ha-automation-row>
</ha-card>
<div
@@ -221,10 +247,12 @@ export default class HaScriptFieldRow extends LitElement {
} satisfies ScriptFieldSidebarConfig);
if (this.narrow) {
requestAnimationFrame(() => {
this.scrollIntoView({
block: "start",
behavior: "smooth",
});
});
}
}