mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-21 08:16:36 +00:00
Add ha-sortable component (#19294)
This commit is contained in:
parent
17e62c10d4
commit
104aef3dec
@ -1,16 +1,13 @@
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDrag } from "@mdi/js";
|
||||
import { LitElement, PropertyValues, css, html, nothing } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { SortableEvent } from "sortablejs";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||
import type { SelectOption, SelectSelector } from "../../data/selector";
|
||||
import { sortableStyles } from "../../resources/ha-sortable-style";
|
||||
import { SortableInstance } from "../../resources/sortable";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../chips/ha-chip-set";
|
||||
import "../chips/ha-input-chip";
|
||||
@ -21,6 +18,7 @@ import "../ha-formfield";
|
||||
import "../ha-input-helper-text";
|
||||
import "../ha-radio";
|
||||
import "../ha-select";
|
||||
import "../ha-sortable";
|
||||
|
||||
@customElement("ha-selector-select")
|
||||
export class HaSelectSelector extends LitElement {
|
||||
@ -42,50 +40,10 @@ export class HaSelectSelector extends LitElement {
|
||||
|
||||
@query("ha-combo-box", true) private comboBox!: HaComboBox;
|
||||
|
||||
private _sortable?: SortableInstance;
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
if (changedProps.has("value") || changedProps.has("selector")) {
|
||||
const sortableNeeded =
|
||||
this.selector.select?.multiple &&
|
||||
this.selector.select.reorder &&
|
||||
this.value?.length;
|
||||
if (!this._sortable && sortableNeeded) {
|
||||
this._createSortable();
|
||||
} else if (this._sortable && !sortableNeeded) {
|
||||
this._destroySortable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _createSortable() {
|
||||
const Sortable = (await import("../../resources/sortable")).default;
|
||||
this._sortable = new Sortable(
|
||||
this.shadowRoot!.querySelector("ha-chip-set")!,
|
||||
{
|
||||
animation: 150,
|
||||
fallbackClass: "sortable-fallback",
|
||||
draggable: "ha-input-chip",
|
||||
onChoose: (evt: SortableEvent) => {
|
||||
(evt.item as any).placeholder =
|
||||
document.createComment("sort-placeholder");
|
||||
evt.item.after((evt.item as any).placeholder);
|
||||
},
|
||||
onEnd: (evt: SortableEvent) => {
|
||||
// put back in original location
|
||||
if ((evt.item as any).placeholder) {
|
||||
(evt.item as any).placeholder.replaceWith(evt.item);
|
||||
delete (evt.item as any).placeholder;
|
||||
}
|
||||
this._dragged(evt);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _dragged(ev: SortableEvent): void {
|
||||
if (ev.oldIndex === ev.newIndex) return;
|
||||
this._move(ev.oldIndex!, ev.newIndex!);
|
||||
private _itemMoved(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
this._move(oldIndex!, newIndex);
|
||||
}
|
||||
|
||||
private _move(index: number, newIndex: number) {
|
||||
@ -99,11 +57,6 @@ export class HaSelectSelector extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _destroySortable() {
|
||||
this._sortable?.destroy();
|
||||
this._sortable = undefined;
|
||||
}
|
||||
|
||||
private _filter = "";
|
||||
|
||||
protected render() {
|
||||
@ -195,14 +148,19 @@ export class HaSelectSelector extends LitElement {
|
||||
return html`
|
||||
${value?.length
|
||||
? html`
|
||||
<ha-sortable
|
||||
no-style
|
||||
.disabled=${!this.selector.select.reorder}
|
||||
@item-moved=${this._itemMoved}
|
||||
>
|
||||
<ha-chip-set>
|
||||
${repeat(
|
||||
value,
|
||||
(item) => item,
|
||||
(item, idx) => {
|
||||
const label =
|
||||
options.find((option) => option.value === item)?.label ||
|
||||
item;
|
||||
options.find((option) => option.value === item)
|
||||
?.label || item;
|
||||
return html`
|
||||
<ha-input-chip
|
||||
.idx=${idx}
|
||||
@ -226,6 +184,7 @@ export class HaSelectSelector extends LitElement {
|
||||
}
|
||||
)}
|
||||
</ha-chip-set>
|
||||
</ha-sortable>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
@ -419,9 +378,7 @@ export class HaSelectSelector extends LitElement {
|
||||
this.comboBox.filteredItems = filteredItems;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
sortableStyles,
|
||||
css`
|
||||
static styles = css`
|
||||
:host {
|
||||
position: relative;
|
||||
}
|
||||
@ -436,8 +393,20 @@ export class HaSelectSelector extends LitElement {
|
||||
ha-chip-set {
|
||||
padding: 8px 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
.sortable-fallback {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.sortable-ghost {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.sortable-drag {
|
||||
cursor: grabbing;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@ -33,7 +33,6 @@ import {
|
||||
} from "lit";
|
||||
import { customElement, eventOptions, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { guard } from "lit/directives/guard";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { storage } from "../common/decorators/storage";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
@ -50,12 +49,12 @@ import { subscribeRepairsIssueRegistry } from "../data/repairs";
|
||||
import { UpdateEntity, updateCanInstall } from "../data/update";
|
||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||
import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
|
||||
import type { SortableInstance } from "../resources/sortable";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant, PanelInfo, Route } from "../types";
|
||||
import "./ha-icon";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-menu-button";
|
||||
import "./ha-sortable";
|
||||
import "./ha-svg-icon";
|
||||
import "./user/ha-user-badge";
|
||||
|
||||
@ -204,15 +203,13 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _issuesCount = 0;
|
||||
|
||||
@state() private _renderEmptySortable = false;
|
||||
|
||||
private _mouseLeaveTimeout?: number;
|
||||
|
||||
private _tooltipHideTimeout?: number;
|
||||
|
||||
private _recentKeydownActiveUntil = 0;
|
||||
|
||||
private sortableStyleLoaded = false;
|
||||
private _editStyleLoaded = false;
|
||||
|
||||
@storage({
|
||||
key: "sidebarPanelOrder",
|
||||
@ -228,8 +225,6 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
})
|
||||
private _hiddenPanels: string[] = [];
|
||||
|
||||
private _sortable?: SortableInstance;
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return this.hass.user?.is_admin
|
||||
? [
|
||||
@ -264,14 +259,13 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
changedProps.has("expanded") ||
|
||||
changedProps.has("narrow") ||
|
||||
changedProps.has("alwaysExpand") ||
|
||||
changedProps.has("editMode") ||
|
||||
changedProps.has("_externalConfig") ||
|
||||
changedProps.has("_updatesCount") ||
|
||||
changedProps.has("_issuesCount") ||
|
||||
changedProps.has("_notifications") ||
|
||||
changedProps.has("editMode") ||
|
||||
changedProps.has("_renderEmptySortable") ||
|
||||
changedProps.has("_hiddenPanels") ||
|
||||
(changedProps.has("_panelOrder") && !this.editMode)
|
||||
changedProps.has("_panelOrder")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@ -306,12 +300,8 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
if (changedProps.has("alwaysExpand")) {
|
||||
toggleAttribute(this, "expanded", this.alwaysExpand);
|
||||
}
|
||||
if (changedProps.has("editMode")) {
|
||||
if (this.editMode) {
|
||||
this._activateEditMode();
|
||||
} else {
|
||||
this._deactivateEditMode();
|
||||
}
|
||||
if (changedProps.has("editMode") && this.editMode) {
|
||||
this._editModeActivated();
|
||||
}
|
||||
if (!changedProps.has("hass")) {
|
||||
return;
|
||||
@ -470,15 +460,36 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
private _panelMoved(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
|
||||
const [beforeSpacer] = computePanels(
|
||||
this.hass.panels,
|
||||
this.hass.defaultPanel,
|
||||
this._panelOrder,
|
||||
this._hiddenPanels,
|
||||
this.hass.locale
|
||||
);
|
||||
|
||||
const panelOrder = beforeSpacer.map((panel) => panel.url_path);
|
||||
const panel = panelOrder.splice(oldIndex, 1)[0];
|
||||
panelOrder.splice(newIndex, 0, panel);
|
||||
|
||||
this._panelOrder = panelOrder;
|
||||
}
|
||||
|
||||
private _renderPanelsEdit(beforeSpacer: PanelInfo[]) {
|
||||
// prettier-ignore
|
||||
return html`<div id="sortable">
|
||||
${guard([this._hiddenPanels, this._renderEmptySortable], () =>
|
||||
this._renderEmptySortable ? "" : this._renderPanels(beforeSpacer)
|
||||
)}
|
||||
</div>
|
||||
${this._renderSpacer()}
|
||||
${this._renderHiddenPanels()} `;
|
||||
return html`
|
||||
<ha-sortable
|
||||
handle-selector="paper-icon-item"
|
||||
.disabled=${!this.editMode}
|
||||
@item-moved=${this._panelMoved}
|
||||
>
|
||||
<div class="reorder-list">${this._renderPanels(beforeSpacer)}</div>
|
||||
</ha-sortable>
|
||||
${this._renderSpacer()}${this._renderHiddenPanels()}
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderHiddenPanels() {
|
||||
@ -674,44 +685,22 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
fireEvent(this, "hass-edit-sidebar", { editMode: true });
|
||||
}
|
||||
|
||||
private async _activateEditMode() {
|
||||
await Promise.all([this._loadSortableStyle(), this._createSortable()]);
|
||||
private async _editModeActivated() {
|
||||
await this._loadEditStyle();
|
||||
}
|
||||
|
||||
private async _loadSortableStyle() {
|
||||
if (this.sortableStyleLoaded) return;
|
||||
private async _loadEditStyle() {
|
||||
if (this._editStyleLoaded) return;
|
||||
|
||||
const sortStylesImport = await import("../resources/ha-sortable-style");
|
||||
const editStylesImport = await import("../resources/ha-sidebar-edit-style");
|
||||
|
||||
const style = document.createElement("style");
|
||||
style.innerHTML = (sortStylesImport.sortableStyles as CSSResult).cssText;
|
||||
style.innerHTML = (editStylesImport.sidebarEditStyle as CSSResult).cssText;
|
||||
this.shadowRoot!.appendChild(style);
|
||||
|
||||
this.sortableStyleLoaded = true;
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
private async _createSortable() {
|
||||
const Sortable = (await import("../resources/sortable")).default;
|
||||
this._sortable = new Sortable(
|
||||
this.shadowRoot!.getElementById("sortable")!,
|
||||
{
|
||||
animation: 150,
|
||||
fallbackClass: "sortable-fallback",
|
||||
dataIdAttr: "data-panel",
|
||||
handle: "paper-icon-item",
|
||||
onSort: async () => {
|
||||
this._panelOrder = this._sortable!.toArray();
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _deactivateEditMode() {
|
||||
this._sortable?.destroy();
|
||||
this._sortable = undefined;
|
||||
}
|
||||
|
||||
private _closeEditMode() {
|
||||
fireEvent(this, "hass-edit-sidebar", { editMode: false });
|
||||
}
|
||||
@ -724,13 +713,8 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
// Make a copy for Memoize
|
||||
this._hiddenPanels = [...this._hiddenPanels, panel];
|
||||
this._renderEmptySortable = true;
|
||||
await this.updateComplete;
|
||||
const container = this.shadowRoot!.getElementById("sortable")!;
|
||||
while (container.lastElementChild) {
|
||||
container.removeChild(container.lastElementChild);
|
||||
}
|
||||
this._renderEmptySortable = false;
|
||||
// Remove it from the panel order
|
||||
this._panelOrder = this._panelOrder.filter((order) => order !== panel);
|
||||
}
|
||||
|
||||
private async _unhidePanel(ev: Event) {
|
||||
@ -739,13 +723,6 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
this._hiddenPanels = this._hiddenPanels.filter(
|
||||
(hidden) => hidden !== panel
|
||||
);
|
||||
this._renderEmptySortable = true;
|
||||
await this.updateComplete;
|
||||
const container = this.shadowRoot!.getElementById("sortable")!;
|
||||
while (container.lastElementChild) {
|
||||
container.removeChild(container.lastElementChild);
|
||||
}
|
||||
this._renderEmptySortable = false;
|
||||
}
|
||||
|
||||
private _itemMouseEnter(ev: MouseEvent) {
|
||||
@ -910,7 +887,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
.menu mwc-button {
|
||||
width: 100%;
|
||||
}
|
||||
#sortable,
|
||||
.reorder-list,
|
||||
.hidden-panel {
|
||||
display: none;
|
||||
}
|
||||
|
153
src/components/ha-sortable.ts
Normal file
153
src/components/ha-sortable.ts
Normal file
@ -0,0 +1,153 @@
|
||||
/* eslint-disable lit/prefer-static-styles */
|
||||
import { html, LitElement, nothing, PropertyValues } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import type { SortableEvent } from "sortablejs";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { SortableInstance } from "../resources/sortable";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"item-moved": {
|
||||
oldIndex: number;
|
||||
newIndex: number;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ha-sortable")
|
||||
export class HaSortable extends LitElement {
|
||||
private _sortable?: SortableInstance;
|
||||
|
||||
@property({ type: Boolean })
|
||||
public disabled = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "no-style" })
|
||||
public noStyle: boolean = false;
|
||||
|
||||
@property({ type: String, attribute: "draggable-selector" })
|
||||
public draggableSelector?: string;
|
||||
|
||||
@property({ type: String, attribute: "handle-selector" })
|
||||
public handleSelector?: string;
|
||||
|
||||
protected updated(changedProperties: PropertyValues<this>) {
|
||||
if (changedProperties.has("disabled")) {
|
||||
if (this.disabled) {
|
||||
this._destroySortable();
|
||||
} else {
|
||||
this._createSortable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for connectedCallback just after disconnectedCallback (when dragging sortable with sortable children)
|
||||
private _shouldBeDestroy = false;
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._shouldBeDestroy = true;
|
||||
setTimeout(() => {
|
||||
if (this._shouldBeDestroy) {
|
||||
this._destroySortable();
|
||||
this._shouldBeDestroy = false;
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._shouldBeDestroy = false;
|
||||
}
|
||||
|
||||
protected createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (this.noStyle) return nothing;
|
||||
return html`
|
||||
<style>
|
||||
.sortable-fallback {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.sortable-ghost {
|
||||
border: 2px solid var(--primary-color);
|
||||
background: rgba(var(--rgb-primary-color), 0.25);
|
||||
border-radius: 4px;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.sortable-drag {
|
||||
border-radius: 4px;
|
||||
opacity: 1;
|
||||
background: var(--card-background-color);
|
||||
box-shadow: 0px 4px 8px 3px #00000026;
|
||||
cursor: grabbing;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _createSortable() {
|
||||
if (this._sortable) return;
|
||||
const container = this.children[0] as HTMLElement | undefined;
|
||||
|
||||
if (!container) return;
|
||||
|
||||
const Sortable = (await import("../resources/sortable")).default;
|
||||
|
||||
const options: SortableInstance.Options = {
|
||||
animation: 150,
|
||||
onChoose: this._handleChoose,
|
||||
onEnd: this._handleEnd,
|
||||
};
|
||||
|
||||
if (this.draggableSelector) {
|
||||
options.draggable = this.draggableSelector;
|
||||
}
|
||||
if (this.handleSelector) {
|
||||
options.handle = this.handleSelector;
|
||||
}
|
||||
this._sortable = new Sortable(container, options);
|
||||
}
|
||||
|
||||
private _handleEnd = (evt: SortableEvent) => {
|
||||
// put back in original location
|
||||
if ((evt.item as any).placeholder) {
|
||||
(evt.item as any).placeholder.replaceWith(evt.item);
|
||||
delete (evt.item as any).placeholder;
|
||||
}
|
||||
// if item was not moved, ignore
|
||||
if (
|
||||
evt.oldIndex === undefined ||
|
||||
evt.newIndex === undefined ||
|
||||
evt.oldIndex === evt.newIndex
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
fireEvent(this, "item-moved", {
|
||||
oldIndex: evt.oldIndex!,
|
||||
newIndex: evt.newIndex!,
|
||||
});
|
||||
};
|
||||
|
||||
private _handleChoose = (evt: SortableEvent) => {
|
||||
(evt.item as any).placeholder = document.createComment("sort-placeholder");
|
||||
evt.item.after((evt.item as any).placeholder);
|
||||
};
|
||||
|
||||
private _destroySortable() {
|
||||
if (!this._sortable) return;
|
||||
this._sortable.destroy();
|
||||
this._sortable = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-sortable": HaSortable;
|
||||
}
|
||||
}
|
@ -4,15 +4,13 @@ import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import type { SortableEvent } from "sortablejs";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { AreaFilterValue } from "../../components/ha-area-filter";
|
||||
import "../../components/ha-button";
|
||||
import "../../components/ha-icon-button";
|
||||
import "../../components/ha-list-item";
|
||||
import "../../components/ha-sortable";
|
||||
import { areaCompare } from "../../data/area_registry";
|
||||
import { sortableStyles } from "../../resources/ha-sortable-style";
|
||||
import type { SortableInstance } from "../../resources/sortable";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { HassDialog } from "../make-dialog-manager";
|
||||
@ -31,23 +29,18 @@ export class DialogAreaFilter
|
||||
|
||||
@state() private _areas: string[] = [];
|
||||
|
||||
private _sortable?: SortableInstance;
|
||||
|
||||
public async showDialog(dialogParams: AreaFilterDialogParams): Promise<void> {
|
||||
public showDialog(dialogParams: AreaFilterDialogParams): void {
|
||||
this._dialogParams = dialogParams;
|
||||
this._hidden = dialogParams.initialValue?.hidden ?? [];
|
||||
const order = dialogParams.initialValue?.order ?? [];
|
||||
const allAreas = Object.keys(this.hass!.areas);
|
||||
this._areas = allAreas.concat().sort(areaCompare(this.hass!.areas, order));
|
||||
await this.updateComplete;
|
||||
this._createSortable();
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._dialogParams = undefined;
|
||||
this._hidden = [];
|
||||
this._areas = [];
|
||||
this._destroySortable();
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
@ -66,42 +59,14 @@ export class DialogAreaFilter
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
private async _createSortable() {
|
||||
const Sortable = (await import("../../resources/sortable")).default;
|
||||
if (this._sortable) return;
|
||||
this._sortable = new Sortable(this.shadowRoot!.querySelector(".areas")!, {
|
||||
animation: 150,
|
||||
fallbackClass: "sortable-fallback",
|
||||
handle: ".handle",
|
||||
draggable: ".draggable",
|
||||
onChoose: (evt: SortableEvent) => {
|
||||
(evt.item as any).placeholder =
|
||||
document.createComment("sort-placeholder");
|
||||
evt.item.after((evt.item as any).placeholder);
|
||||
},
|
||||
onEnd: (evt: SortableEvent) => {
|
||||
// put back in original location
|
||||
if ((evt.item as any).placeholder) {
|
||||
(evt.item as any).placeholder.replaceWith(evt.item);
|
||||
delete (evt.item as any).placeholder;
|
||||
}
|
||||
this._dragged(evt);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _destroySortable() {
|
||||
this._sortable?.destroy();
|
||||
this._sortable = undefined;
|
||||
}
|
||||
|
||||
private _dragged(ev: SortableEvent): void {
|
||||
if (ev.oldIndex === ev.newIndex) return;
|
||||
private _areaMoved(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
|
||||
const areas = this._areas.concat();
|
||||
|
||||
const option = areas.splice(ev.oldIndex!, 1)[0];
|
||||
areas.splice(ev.newIndex!, 0, option);
|
||||
const option = areas.splice(oldIndex, 1)[0];
|
||||
areas.splice(newIndex, 0, option);
|
||||
|
||||
this._areas = areas;
|
||||
}
|
||||
@ -119,6 +84,11 @@ export class DialogAreaFilter
|
||||
@closed=${this._cancel}
|
||||
.heading=${this._dialogParams.title ??
|
||||
this.hass.localize("ui.components.area-filter.title")}
|
||||
>
|
||||
<ha-sortable
|
||||
draggable-selector=".draggable"
|
||||
handle-selector=".handle"
|
||||
@item-moved=${this._areaMoved}
|
||||
>
|
||||
<mwc-list class="areas">
|
||||
${repeat(
|
||||
@ -164,6 +134,7 @@ export class DialogAreaFilter
|
||||
}
|
||||
)}
|
||||
</mwc-list>
|
||||
</ha-sortable>
|
||||
<ha-button slot="secondaryAction" dialogAction="cancel">
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
@ -192,7 +163,6 @@ export class DialogAreaFilter
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
sortableStyles,
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-dialog {
|
||||
|
@ -10,9 +10,9 @@ import {
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import type { SortableEvent } from "sortablejs";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-control-slider";
|
||||
import "../../../../components/ha-sortable";
|
||||
import { UNAVAILABLE } from "../../../../data/entity";
|
||||
import {
|
||||
ExtEntityRegistryEntry,
|
||||
@ -24,7 +24,6 @@ import {
|
||||
computeDefaultFavoriteColors,
|
||||
} from "../../../../data/light";
|
||||
import { actionHandler } from "../../../../panels/lovelace/common/directives/action-handler-directive";
|
||||
import type { SortableInstance } from "../../../../resources/sortable";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { showConfirmationDialog } from "../../../generic/show-dialog-box";
|
||||
import "./ha-favorite-color-button";
|
||||
@ -48,16 +47,7 @@ export class HaMoreInfoLightFavoriteColors extends LitElement {
|
||||
|
||||
@state() private _favoriteColors: LightColor[] = [];
|
||||
|
||||
private _sortable?: SortableInstance;
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
if (changedProps.has("editMode")) {
|
||||
if (this.editMode) {
|
||||
this._createSortable();
|
||||
} else {
|
||||
this._destroySortable();
|
||||
}
|
||||
}
|
||||
if (changedProps.has("entry")) {
|
||||
if (this.entry) {
|
||||
if (this.entry.options?.light?.favorite_colors) {
|
||||
@ -69,34 +59,10 @@ export class HaMoreInfoLightFavoriteColors extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _createSortable() {
|
||||
const Sortable = (await import("../../../../resources/sortable")).default;
|
||||
this._sortable = new Sortable(
|
||||
this.shadowRoot!.querySelector(".container")!,
|
||||
{
|
||||
animation: 150,
|
||||
fallbackClass: "sortable-fallback",
|
||||
draggable: ".color",
|
||||
onChoose: (evt: SortableEvent) => {
|
||||
(evt.item as any).placeholder =
|
||||
document.createComment("sort-placeholder");
|
||||
evt.item.after((evt.item as any).placeholder);
|
||||
},
|
||||
onEnd: (evt: SortableEvent) => {
|
||||
// put back in original location
|
||||
if ((evt.item as any).placeholder) {
|
||||
(evt.item as any).placeholder.replaceWith(evt.item);
|
||||
delete (evt.item as any).placeholder;
|
||||
}
|
||||
this._dragged(evt);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _dragged(ev: SortableEvent): void {
|
||||
if (ev.oldIndex === ev.newIndex) return;
|
||||
this._move(ev.oldIndex!, ev.newIndex!);
|
||||
private _colorMoved(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
this._move(oldIndex, newIndex);
|
||||
}
|
||||
|
||||
private _move(index: number, newIndex: number) {
|
||||
@ -107,11 +73,6 @@ export class HaMoreInfoLightFavoriteColors extends LitElement {
|
||||
this._save(favoriteColors);
|
||||
}
|
||||
|
||||
private _destroySortable() {
|
||||
this._sortable?.destroy();
|
||||
this._sortable = undefined;
|
||||
}
|
||||
|
||||
private _apply = (index: number) => {
|
||||
const favorite = this._favoriteColors[index];
|
||||
this.hass.callService("light", "turn_on", {
|
||||
@ -223,6 +184,12 @@ export class HaMoreInfoLightFavoriteColors extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-sortable
|
||||
@item-moved=${this._colorMoved}
|
||||
item=".color"
|
||||
no-style
|
||||
.disabled=${!this.editMode}
|
||||
>
|
||||
<div class="container">
|
||||
${this._favoriteColors.map(
|
||||
(color, index) => html`
|
||||
@ -289,6 +256,7 @@ export class HaMoreInfoLightFavoriteColors extends LitElement {
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-sortable>
|
||||
`;
|
||||
}
|
||||
|
||||
|
@ -1,19 +1,16 @@
|
||||
import "@material/mwc-button";
|
||||
import { mdiArrowDown, mdiArrowUp, mdiDrag, mdiPlus } from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import { CSSResultGroup, LitElement, PropertyValues, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import type { SortableEvent } from "sortablejs";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import "../../../../components/ha-sortable";
|
||||
import { getService, isService } from "../../../../data/action";
|
||||
import type { AutomationClipboard } from "../../../../data/automation";
|
||||
import { Action } from "../../../../data/script";
|
||||
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
||||
import type { SortableInstance } from "../../../../resources/sortable";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import {
|
||||
PASTE_VALUE,
|
||||
@ -48,8 +45,6 @@ export default class HaAutomationAction extends LitElement {
|
||||
|
||||
private _actionKeys = new WeakMap<Action, string>();
|
||||
|
||||
private _sortable?: SortableInstance;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
${this.reOrderMode && !this.nested
|
||||
@ -63,14 +58,19 @@ export default class HaAutomationAction extends LitElement {
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.description_actions"
|
||||
)}
|
||||
<mwc-button slot="action" @click=${this._exitReOrderMode}>
|
||||
<ha-button slot="action" @click=${this._exitReOrderMode}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.exit"
|
||||
)}
|
||||
</mwc-button>
|
||||
</ha-button>
|
||||
</ha-alert>
|
||||
`
|
||||
: null}
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
.disabled=${!this.reOrderMode}
|
||||
@item-moved=${this._actionMoved}
|
||||
>
|
||||
<div class="actions">
|
||||
${repeat(
|
||||
this.actions,
|
||||
@ -119,6 +119,7 @@ export default class HaAutomationAction extends LitElement {
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</ha-sortable>
|
||||
<div class="buttons">
|
||||
<ha-button
|
||||
outlined
|
||||
@ -146,13 +147,6 @@ export default class HaAutomationAction extends LitElement {
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (changedProps.has("reOrderMode")) {
|
||||
if (this.reOrderMode) {
|
||||
this._createSortable();
|
||||
} else {
|
||||
this._destroySortable();
|
||||
}
|
||||
}
|
||||
if (changedProps.has("actions") && this._focusLastActionOnChange) {
|
||||
this._focusLastActionOnChange = false;
|
||||
|
||||
@ -215,33 +209,6 @@ export default class HaAutomationAction extends LitElement {
|
||||
this.reOrderMode = false;
|
||||
}
|
||||
|
||||
private async _createSortable() {
|
||||
const Sortable = (await import("../../../../resources/sortable")).default;
|
||||
this._sortable = new Sortable(this.shadowRoot!.querySelector(".actions")!, {
|
||||
animation: 150,
|
||||
fallbackClass: "sortable-fallback",
|
||||
handle: ".handle",
|
||||
onChoose: (evt: SortableEvent) => {
|
||||
(evt.item as any).placeholder =
|
||||
document.createComment("sort-placeholder");
|
||||
evt.item.after((evt.item as any).placeholder);
|
||||
},
|
||||
onEnd: (evt: SortableEvent) => {
|
||||
// put back in original location
|
||||
if ((evt.item as any).placeholder) {
|
||||
(evt.item as any).placeholder.replaceWith(evt.item);
|
||||
delete (evt.item as any).placeholder;
|
||||
}
|
||||
this._dragged(evt);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _destroySortable() {
|
||||
this._sortable?.destroy();
|
||||
this._sortable = undefined;
|
||||
}
|
||||
|
||||
private _getKey(action: Action) {
|
||||
if (!this._actionKeys.has(action)) {
|
||||
this._actionKeys.set(action, Math.random().toString());
|
||||
@ -262,11 +229,6 @@ export default class HaAutomationAction extends LitElement {
|
||||
this._move(index, newIndex);
|
||||
}
|
||||
|
||||
private _dragged(ev: SortableEvent): void {
|
||||
if (ev.oldIndex === ev.newIndex) return;
|
||||
this._move(ev.oldIndex!, ev.newIndex!);
|
||||
}
|
||||
|
||||
private _move(index: number, newIndex: number) {
|
||||
const actions = this.actions.concat();
|
||||
const action = actions.splice(index, 1)[0];
|
||||
@ -274,6 +236,12 @@ export default class HaAutomationAction extends LitElement {
|
||||
fireEvent(this, "value-changed", { value: actions });
|
||||
}
|
||||
|
||||
private _actionMoved(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
this._move(oldIndex, newIndex);
|
||||
}
|
||||
|
||||
private _actionChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const actions = [...this.actions];
|
||||
@ -302,9 +270,7 @@ export default class HaAutomationAction extends LitElement {
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
sortableStyles,
|
||||
css`
|
||||
return css`
|
||||
ha-automation-action-row {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
@ -320,9 +286,9 @@ export default class HaAutomationAction extends LitElement {
|
||||
overflow: hidden;
|
||||
}
|
||||
.handle {
|
||||
padding: 12px;
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
cursor: grab;
|
||||
padding: 12px;
|
||||
}
|
||||
.handle ha-svg-icon {
|
||||
pointer-events: none;
|
||||
@ -333,8 +299,7 @@ export default class HaAutomationAction extends LitElement {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,29 +1,31 @@
|
||||
import { consume } from "@lit-labs/context";
|
||||
import type { SortableEvent } from "sortablejs";
|
||||
import type { ActionDetail } from "@material/mwc-list";
|
||||
import {
|
||||
mdiDotsVertical,
|
||||
mdiRenameBox,
|
||||
mdiSort,
|
||||
mdiArrowDown,
|
||||
mdiArrowUp,
|
||||
mdiContentDuplicate,
|
||||
mdiDelete,
|
||||
mdiPlus,
|
||||
mdiArrowUp,
|
||||
mdiArrowDown,
|
||||
mdiDotsVertical,
|
||||
mdiDrag,
|
||||
mdiPlus,
|
||||
mdiRenameBox,
|
||||
mdiSort,
|
||||
} from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import { CSSResultGroup, LitElement, PropertyValues, css, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import type { ActionDetail } from "@material/mwc-list";
|
||||
import type { SortableInstance } from "../../../../../resources/sortable";
|
||||
import { ensureArray } from "../../../../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { capitalizeFirstLetter } from "../../../../../common/string/capitalize-first-letter";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../../../components/ha-button-menu";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../../../components/ha-sortable";
|
||||
import { Condition } from "../../../../../data/automation";
|
||||
import { describeCondition } from "../../../../../data/automation_i18n";
|
||||
import { fullEntitiesContext } from "../../../../../data/context";
|
||||
import { EntityRegistryEntry } from "../../../../../data/entity_registry";
|
||||
import {
|
||||
Action,
|
||||
ChooseAction,
|
||||
@ -36,10 +38,6 @@ import {
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { ActionElement } from "../ha-automation-action-row";
|
||||
import { describeCondition } from "../../../../../data/automation_i18n";
|
||||
import { fullEntitiesContext } from "../../../../../data/context";
|
||||
import { EntityRegistryEntry } from "../../../../../data/entity_registry";
|
||||
import { sortableStyles } from "../../../../../resources/ha-sortable-style";
|
||||
|
||||
const preventDefault = (ev) => ev.preventDefault();
|
||||
|
||||
@ -63,8 +61,6 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
|
||||
private _expandLast = false;
|
||||
|
||||
private _sortable?: SortableInstance;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return { choose: [{ conditions: [], sequence: [] }] };
|
||||
}
|
||||
@ -100,12 +96,18 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
const action = this.action;
|
||||
|
||||
return html`
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
.disabled=${!this.reOrderMode}
|
||||
@item-moved=${this._optionMoved}
|
||||
>
|
||||
<div class="options">
|
||||
${repeat(
|
||||
action.choose ? ensureArray(action.choose) : [],
|
||||
(option) => option,
|
||||
(option, idx) =>
|
||||
html`<ha-card>
|
||||
(option, idx) => html`
|
||||
<div class="option">
|
||||
<ha-card>
|
||||
<ha-expansion-panel
|
||||
.index=${idx}
|
||||
leftChevron
|
||||
@ -248,9 +250,12 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
></ha-automation-action>
|
||||
</div>
|
||||
</ha-expansion-panel>
|
||||
</ha-card>`
|
||||
</ha-card>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</ha-sortable>
|
||||
<ha-button
|
||||
outlined
|
||||
.label=${this.hass.localize(
|
||||
@ -352,14 +357,6 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (changedProps.has("reOrderMode")) {
|
||||
if (this.reOrderMode) {
|
||||
this._createSortable();
|
||||
} else {
|
||||
this._destroySortable();
|
||||
}
|
||||
}
|
||||
|
||||
if (this._expandLast) {
|
||||
const nodes = this.shadowRoot!.querySelectorAll("ha-expansion-panel");
|
||||
nodes[nodes.length - 1].expanded = true;
|
||||
@ -425,11 +422,6 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
this._move(index, newIndex);
|
||||
}
|
||||
|
||||
private _dragged(ev: SortableEvent): void {
|
||||
if (ev.oldIndex === ev.newIndex) return;
|
||||
this._move(ev.oldIndex!, ev.newIndex!);
|
||||
}
|
||||
|
||||
private _move(index: number, newIndex: number) {
|
||||
const options = ensureArray(this.action.choose)!.concat();
|
||||
const item = options.splice(index, 1)[0];
|
||||
@ -443,6 +435,12 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _optionMoved(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
this._move(oldIndex, newIndex);
|
||||
}
|
||||
|
||||
private _removeOption(ev: CustomEvent) {
|
||||
const index = (ev.target as any).idx;
|
||||
showConfirmationDialog(this, {
|
||||
@ -479,40 +477,11 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async _createSortable() {
|
||||
const Sortable = (await import("../../../../../resources/sortable"))
|
||||
.default;
|
||||
this._sortable = new Sortable(this.shadowRoot!.querySelector(".options")!, {
|
||||
animation: 150,
|
||||
fallbackClass: "sortable-fallback",
|
||||
handle: ".handle",
|
||||
onChoose: (evt: SortableEvent) => {
|
||||
(evt.item as any).placeholder =
|
||||
document.createComment("sort-placeholder");
|
||||
evt.item.after((evt.item as any).placeholder);
|
||||
},
|
||||
onEnd: (evt: SortableEvent) => {
|
||||
// put back in original location
|
||||
if ((evt.item as any).placeholder) {
|
||||
(evt.item as any).placeholder.replaceWith(evt.item);
|
||||
delete (evt.item as any).placeholder;
|
||||
}
|
||||
this._dragged(evt);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _destroySortable() {
|
||||
this._sortable?.destroy();
|
||||
this._sortable = undefined;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
sortableStyles,
|
||||
css`
|
||||
ha-card {
|
||||
.option {
|
||||
margin: 0 0 16px 0;
|
||||
}
|
||||
.add-card mwc-button {
|
||||
@ -543,9 +512,9 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
padding: 0 16px 16px 16px;
|
||||
}
|
||||
.handle {
|
||||
padding: 12px;
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
cursor: grab;
|
||||
padding: 12px;
|
||||
}
|
||||
.handle ha-svg-icon {
|
||||
pointer-events: none;
|
||||
|
@ -1,4 +1,3 @@
|
||||
import "@material/mwc-button";
|
||||
import { mdiArrowDown, mdiArrowUp, mdiDrag, mdiPlus } from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import {
|
||||
@ -11,18 +10,16 @@ import {
|
||||
} from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import type { SortableEvent } from "sortablejs";
|
||||
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-sortable";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import type {
|
||||
AutomationClipboard,
|
||||
Condition,
|
||||
} from "../../../../data/automation";
|
||||
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
||||
import type { SortableInstance } from "../../../../resources/sortable";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import {
|
||||
PASTE_VALUE,
|
||||
@ -55,17 +52,7 @@ export default class HaAutomationCondition extends LitElement {
|
||||
|
||||
private _conditionKeys = new WeakMap<Condition, string>();
|
||||
|
||||
private _sortable?: SortableInstance;
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
if (changedProperties.has("reOrderMode")) {
|
||||
if (this.reOrderMode) {
|
||||
this._createSortable();
|
||||
} else {
|
||||
this._destroySortable();
|
||||
}
|
||||
}
|
||||
|
||||
if (!changedProperties.has("conditions")) {
|
||||
return;
|
||||
}
|
||||
@ -118,14 +105,20 @@ export default class HaAutomationCondition extends LitElement {
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.description_conditions"
|
||||
)}
|
||||
<mwc-button slot="action" @click=${this._exitReOrderMode}>
|
||||
<ha-button slot="action" @click=${this._exitReOrderMode}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.exit"
|
||||
)}
|
||||
</mwc-button>
|
||||
</ha-button>
|
||||
</ha-alert>
|
||||
`
|
||||
: null}
|
||||
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
.disabled=${!this.reOrderMode}
|
||||
@item-moved=${this._conditionMoved}
|
||||
>
|
||||
<div class="conditions">
|
||||
${repeat(
|
||||
this.conditions.filter((c) => typeof c === "object"),
|
||||
@ -175,6 +168,7 @@ export default class HaAutomationCondition extends LitElement {
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</ha-sortable>
|
||||
<div class="buttons">
|
||||
<ha-button
|
||||
outlined
|
||||
@ -248,36 +242,6 @@ export default class HaAutomationCondition extends LitElement {
|
||||
this.reOrderMode = false;
|
||||
}
|
||||
|
||||
private async _createSortable() {
|
||||
const Sortable = (await import("../../../../resources/sortable")).default;
|
||||
this._sortable = new Sortable(
|
||||
this.shadowRoot!.querySelector(".conditions")!,
|
||||
{
|
||||
animation: 150,
|
||||
fallbackClass: "sortable-fallback",
|
||||
handle: ".handle",
|
||||
onChoose: (evt: SortableEvent) => {
|
||||
(evt.item as any).placeholder =
|
||||
document.createComment("sort-placeholder");
|
||||
evt.item.after((evt.item as any).placeholder);
|
||||
},
|
||||
onEnd: (evt: SortableEvent) => {
|
||||
// put back in original location
|
||||
if ((evt.item as any).placeholder) {
|
||||
(evt.item as any).placeholder.replaceWith(evt.item);
|
||||
delete (evt.item as any).placeholder;
|
||||
}
|
||||
this._dragged(evt);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _destroySortable() {
|
||||
this._sortable?.destroy();
|
||||
this._sortable = undefined;
|
||||
}
|
||||
|
||||
private _getKey(condition: Condition) {
|
||||
if (!this._conditionKeys.has(condition)) {
|
||||
this._conditionKeys.set(condition, Math.random().toString());
|
||||
@ -298,11 +262,6 @@ export default class HaAutomationCondition extends LitElement {
|
||||
this._move(index, newIndex);
|
||||
}
|
||||
|
||||
private _dragged(ev: SortableEvent): void {
|
||||
if (ev.oldIndex === ev.newIndex) return;
|
||||
this._move(ev.oldIndex!, ev.newIndex!);
|
||||
}
|
||||
|
||||
private _move(index: number, newIndex: number) {
|
||||
const conditions = this.conditions.concat();
|
||||
const condition = conditions.splice(index, 1)[0];
|
||||
@ -310,6 +269,12 @@ export default class HaAutomationCondition extends LitElement {
|
||||
fireEvent(this, "value-changed", { value: conditions });
|
||||
}
|
||||
|
||||
private _conditionMoved(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
this._move(oldIndex, newIndex);
|
||||
}
|
||||
|
||||
private _conditionChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const conditions = [...this.conditions];
|
||||
@ -340,9 +305,7 @@ export default class HaAutomationCondition extends LitElement {
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
sortableStyles,
|
||||
css`
|
||||
return css`
|
||||
ha-automation-condition-row {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
@ -358,9 +321,9 @@ export default class HaAutomationCondition extends LitElement {
|
||||
overflow: hidden;
|
||||
}
|
||||
.handle {
|
||||
padding: 12px;
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
cursor: grab;
|
||||
padding: 12px;
|
||||
}
|
||||
.handle ha-svg-icon {
|
||||
pointer-events: none;
|
||||
@ -371,8 +334,7 @@ export default class HaAutomationCondition extends LitElement {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,25 +1,22 @@
|
||||
import "@material/mwc-button";
|
||||
import { mdiArrowDown, mdiArrowUp, mdiDrag, mdiPlus } from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import { CSSResultGroup, LitElement, PropertyValues, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import type { SortableEvent } from "sortablejs";
|
||||
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-sortable";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { AutomationClipboard, Trigger } from "../../../../data/automation";
|
||||
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
||||
import type { SortableInstance } from "../../../../resources/sortable";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import "./ha-automation-trigger-row";
|
||||
import type HaAutomationTriggerRow from "./ha-automation-trigger-row";
|
||||
import {
|
||||
PASTE_VALUE,
|
||||
showAddAutomationElementDialog,
|
||||
} from "../show-add-automation-element-dialog";
|
||||
import "./ha-automation-trigger-row";
|
||||
import type HaAutomationTriggerRow from "./ha-automation-trigger-row";
|
||||
|
||||
@customElement("ha-automation-trigger")
|
||||
export default class HaAutomationTrigger extends LitElement {
|
||||
@ -45,8 +42,6 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
|
||||
private _triggerKeys = new WeakMap<Trigger, string>();
|
||||
|
||||
private _sortable?: SortableInstance;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
${this.reOrderMode && !this.nested
|
||||
@ -60,14 +55,19 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.description_triggers"
|
||||
)}
|
||||
<mwc-button slot="action" @click=${this._exitReOrderMode}>
|
||||
<ha-button slot="action" @click=${this._exitReOrderMode}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.re_order_mode.exit"
|
||||
)}
|
||||
</mwc-button>
|
||||
</ha-button>
|
||||
</ha-alert>
|
||||
`
|
||||
: null}
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
.disabled=${!this.reOrderMode}
|
||||
@item-moved=${this._triggerMoved}
|
||||
>
|
||||
<div class="triggers">
|
||||
${repeat(
|
||||
this.triggers,
|
||||
@ -113,6 +113,8 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
</ha-automation-trigger-row>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</ha-sortable>
|
||||
<ha-button
|
||||
outlined
|
||||
.label=${this.hass.localize(
|
||||
@ -123,7 +125,6 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||
</ha-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -158,14 +159,6 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (changedProps.has("reOrderMode")) {
|
||||
if (this.reOrderMode) {
|
||||
this._createSortable();
|
||||
} else {
|
||||
this._destroySortable();
|
||||
}
|
||||
}
|
||||
|
||||
if (changedProps.has("triggers") && this._focusLastTriggerOnChange) {
|
||||
this._focusLastTriggerOnChange = false;
|
||||
|
||||
@ -190,36 +183,6 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
this.reOrderMode = false;
|
||||
}
|
||||
|
||||
private async _createSortable() {
|
||||
const Sortable = (await import("../../../../resources/sortable")).default;
|
||||
this._sortable = new Sortable(
|
||||
this.shadowRoot!.querySelector(".triggers")!,
|
||||
{
|
||||
animation: 150,
|
||||
fallbackClass: "sortable-fallback",
|
||||
handle: ".handle",
|
||||
onChoose: (evt: SortableEvent) => {
|
||||
(evt.item as any).placeholder =
|
||||
document.createComment("sort-placeholder");
|
||||
evt.item.after((evt.item as any).placeholder);
|
||||
},
|
||||
onEnd: (evt: SortableEvent) => {
|
||||
// put back in original location
|
||||
if ((evt.item as any).placeholder) {
|
||||
(evt.item as any).placeholder.replaceWith(evt.item);
|
||||
delete (evt.item as any).placeholder;
|
||||
}
|
||||
this._dragged(evt);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _destroySortable() {
|
||||
this._sortable?.destroy();
|
||||
this._sortable = undefined;
|
||||
}
|
||||
|
||||
private _getKey(action: Trigger) {
|
||||
if (!this._triggerKeys.has(action)) {
|
||||
this._triggerKeys.set(action, Math.random().toString());
|
||||
@ -240,11 +203,6 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
this._move(index, newIndex);
|
||||
}
|
||||
|
||||
private _dragged(ev: SortableEvent): void {
|
||||
if (ev.oldIndex === ev.newIndex) return;
|
||||
this._move(ev.oldIndex!, ev.newIndex!);
|
||||
}
|
||||
|
||||
private _move(index: number, newIndex: number) {
|
||||
const triggers = this.triggers.concat();
|
||||
const trigger = triggers.splice(index, 1)[0];
|
||||
@ -252,6 +210,12 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
fireEvent(this, "value-changed", { value: triggers });
|
||||
}
|
||||
|
||||
private _triggerMoved(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
this._move(oldIndex, newIndex);
|
||||
}
|
||||
|
||||
private _triggerChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const triggers = [...this.triggers];
|
||||
@ -280,9 +244,7 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
sortableStyles,
|
||||
css`
|
||||
return css`
|
||||
ha-automation-trigger-row {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
@ -298,16 +260,15 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
overflow: hidden;
|
||||
}
|
||||
.handle {
|
||||
padding: 12px;
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
cursor: grab;
|
||||
padding: 12px;
|
||||
}
|
||||
.handle ha-svg-icon {
|
||||
pointer-events: none;
|
||||
height: 24px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,22 +1,20 @@
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import { mdiDelete, mdiDrag } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import type { SortableEvent } from "sortablejs";
|
||||
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-list-item";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
import "../../../../components/ha-list-item";
|
||||
import "../../../../components/ha-sortable";
|
||||
import "../../../../components/ha-textfield";
|
||||
import type { HaTextField } from "../../../../components/ha-textfield";
|
||||
import type { InputSelect } from "../../../../data/input_select";
|
||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { SortableInstance } from "../../../../resources/sortable";
|
||||
|
||||
@customElement("ha-input_select-form")
|
||||
class HaInputSelectForm extends LitElement {
|
||||
@ -32,59 +30,20 @@ class HaInputSelectForm extends LitElement {
|
||||
|
||||
@state() private _options: string[] = [];
|
||||
|
||||
private _sortable?: SortableInstance;
|
||||
|
||||
@query("#option_input", true) private _optionInput?: HaTextField;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._createSortable();
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._destroySortable();
|
||||
}
|
||||
|
||||
private async _createSortable() {
|
||||
const Sortable = (await import("../../../../resources/sortable")).default;
|
||||
this._sortable = new Sortable(this.shadowRoot!.querySelector(".options")!, {
|
||||
animation: 150,
|
||||
fallbackClass: "sortable-fallback",
|
||||
handle: ".handle",
|
||||
onChoose: (evt: SortableEvent) => {
|
||||
(evt.item as any).placeholder =
|
||||
document.createComment("sort-placeholder");
|
||||
evt.item.after((evt.item as any).placeholder);
|
||||
},
|
||||
onEnd: (evt: SortableEvent) => {
|
||||
// put back in original location
|
||||
if ((evt.item as any).placeholder) {
|
||||
(evt.item as any).placeholder.replaceWith(evt.item);
|
||||
delete (evt.item as any).placeholder;
|
||||
}
|
||||
this._dragged(evt);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _dragged(ev: SortableEvent): void {
|
||||
if (ev.oldIndex === ev.newIndex) return;
|
||||
|
||||
private _optionMoved(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
const options = this._options.concat();
|
||||
const option = options.splice(ev.oldIndex!, 1)[0];
|
||||
options.splice(ev.newIndex!, 0, option);
|
||||
const option = options.splice(oldIndex, 1)[0];
|
||||
options.splice(newIndex, 0, option);
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this._item, options },
|
||||
});
|
||||
}
|
||||
|
||||
private _destroySortable() {
|
||||
this._sortable?.destroy();
|
||||
this._sortable = undefined;
|
||||
}
|
||||
|
||||
set item(item: InputSelect) {
|
||||
this._item = item;
|
||||
if (item) {
|
||||
@ -142,6 +101,7 @@ class HaInputSelectForm extends LitElement {
|
||||
"ui.dialogs.helper_settings.input_select.options"
|
||||
)}:
|
||||
</div>
|
||||
<ha-sortable @item-moved=${this._optionMoved} handle-selector=".handle">
|
||||
<mwc-list class="options">
|
||||
${this._options.length
|
||||
? repeat(
|
||||
@ -175,6 +135,7 @@ class HaInputSelectForm extends LitElement {
|
||||
</ha-list-item>
|
||||
`}
|
||||
</mwc-list>
|
||||
</ha-sortable>
|
||||
<div class="layout horizontal center">
|
||||
<ha-textfield
|
||||
class="flex-auto"
|
||||
@ -255,7 +216,6 @@ class HaInputSelectForm extends LitElement {
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
sortableStyles,
|
||||
css`
|
||||
.form {
|
||||
color: var(--primary-text-color);
|
||||
|
@ -14,7 +14,6 @@ import "../../../components/ha-button";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { Fields } from "../../../data/script";
|
||||
import { sortableStyles } from "../../../resources/ha-sortable-style";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "./ha-script-field-row";
|
||||
import type HaScriptFieldRow from "./ha-script-field-row";
|
||||
@ -142,9 +141,7 @@ export default class HaScriptFields extends LitElement {
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
sortableStyles,
|
||||
css`
|
||||
return css`
|
||||
ha-script-field-row {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
@ -153,8 +150,7 @@ export default class HaScriptFields extends LitElement {
|
||||
ha-svg-icon {
|
||||
height: 20px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,11 +20,10 @@ import {
|
||||
html,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import type { SortableEvent } from "sortablejs";
|
||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import "../../../components/ha-card";
|
||||
@ -35,6 +34,7 @@ import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-markdown-element";
|
||||
import "../../../components/ha-relative-time";
|
||||
import "../../../components/ha-select";
|
||||
import "../../../components/ha-sortable";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-textfield";
|
||||
import type { HaTextField } from "../../../components/ha-textfield";
|
||||
@ -50,14 +50,12 @@ import {
|
||||
updateItem,
|
||||
} from "../../../data/todo";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import type { SortableInstance } from "../../../resources/sortable";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { showTodoItemEditDialog } from "../../todo/show-dialog-todo-item-editor";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { TodoListCardConfig } from "./types";
|
||||
import { sortableStyles } from "../../../resources/ha-sortable-style";
|
||||
|
||||
@customElement("hui-todo-list-card")
|
||||
export class HuiTodoListCard extends LitElement implements LovelaceCard {
|
||||
@ -96,10 +94,6 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
|
||||
|
||||
private _unsubItems?: Promise<UnsubscribeFunc>;
|
||||
|
||||
private _sortable?: SortableInstance;
|
||||
|
||||
@query("#unchecked") private _uncheckedContainer?: HTMLElement;
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
if (this.hasUpdated) {
|
||||
@ -264,9 +258,15 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
|
||||
</ha-button-menu>`
|
||||
: nothing}
|
||||
</div>
|
||||
<ha-sortable
|
||||
handle-selector="ha-svg-icon"
|
||||
.disabled=${!this._reordering}
|
||||
@item-moved=${this._itemMoved}
|
||||
>
|
||||
<mwc-list id="unchecked">
|
||||
${this._renderItems(uncheckedItems, unavailable)}
|
||||
</mwc-list>`
|
||||
</mwc-list>
|
||||
</ha-sortable>`
|
||||
: html`<p class="empty">
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.cards.todo-list.no_unchecked_items"
|
||||
@ -553,43 +553,12 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
|
||||
|
||||
private async _toggleReorder() {
|
||||
this._reordering = !this._reordering;
|
||||
await this.updateComplete;
|
||||
if (this._reordering) {
|
||||
this._createSortable();
|
||||
} else {
|
||||
this._sortable?.destroy();
|
||||
this._sortable = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private async _createSortable() {
|
||||
const Sortable = (await import("../../../resources/sortable")).default;
|
||||
this._sortable = new Sortable(this._uncheckedContainer!, {
|
||||
animation: 150,
|
||||
fallbackClass: "sortable-fallback",
|
||||
dataIdAttr: "item-id",
|
||||
handle: "ha-svg-icon",
|
||||
onChoose: (evt: SortableEvent) => {
|
||||
(evt.item as any).placeholder =
|
||||
document.createComment("sort-placeholder");
|
||||
evt.item.after((evt.item as any).placeholder);
|
||||
},
|
||||
onEnd: (evt: SortableEvent) => {
|
||||
// put back in original location
|
||||
if ((evt.item as any).placeholder) {
|
||||
(evt.item as any).placeholder.replaceWith(evt.item);
|
||||
delete (evt.item as any).placeholder;
|
||||
}
|
||||
if (evt.newIndex === undefined || evt.oldIndex === undefined) {
|
||||
return;
|
||||
}
|
||||
// Since this is `onEnd` event, it's possible that
|
||||
// an item was dragged away and was put back to its original position.
|
||||
if (evt.oldIndex !== evt.newIndex) {
|
||||
this._moveItem(evt.oldIndex, evt.newIndex);
|
||||
}
|
||||
},
|
||||
});
|
||||
private async _itemMoved(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
this._moveItem(oldIndex, newIndex);
|
||||
}
|
||||
|
||||
private async _moveItem(oldIndex: number, newIndex: number) {
|
||||
@ -621,9 +590,7 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
sortableStyles,
|
||||
css`
|
||||
return css`
|
||||
ha-card {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
@ -778,8 +745,7 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
|
||||
.warning {
|
||||
color: var(--error-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ import { mdiDrag } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import type { SortableEvent } from "sortablejs";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/entity/ha-entity-picker";
|
||||
import type {
|
||||
@ -10,8 +9,7 @@ import type {
|
||||
HaEntityPickerEntityFilterFunc,
|
||||
} from "../../../components/entity/ha-entity-picker";
|
||||
import "../../../components/ha-icon-button";
|
||||
import { sortableStyles } from "../../../resources/ha-sortable-style";
|
||||
import type { SortableInstance } from "../../../resources/sortable";
|
||||
import "../../../components/ha-sortable";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { EntityConfig } from "../entity-rows/types";
|
||||
|
||||
@ -27,13 +25,6 @@ export class HuiEntityEditor extends LitElement {
|
||||
|
||||
private _entityKeys = new WeakMap<EntityConfig, string>();
|
||||
|
||||
private _sortable?: SortableInstance;
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._destroySortable();
|
||||
}
|
||||
|
||||
private _getKey(action: EntityConfig) {
|
||||
if (!this._entityKeys.has(action)) {
|
||||
this._entityKeys.set(action, Math.random().toString());
|
||||
@ -55,6 +46,7 @@ export class HuiEntityEditor extends LitElement {
|
||||
this.hass!.localize("ui.panel.lovelace.editor.card.config.required") +
|
||||
")"}
|
||||
</h3>
|
||||
<ha-sortable handle-selector=".handle" @item-moved=${this._entityMoved}>
|
||||
<div class="entities">
|
||||
${repeat(
|
||||
this.entities,
|
||||
@ -76,6 +68,7 @@ export class HuiEntityEditor extends LitElement {
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</ha-sortable>
|
||||
<ha-entity-picker
|
||||
class="add-entity"
|
||||
.hass=${this.hass}
|
||||
@ -85,41 +78,6 @@ export class HuiEntityEditor extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this._createSortable();
|
||||
}
|
||||
|
||||
private async _createSortable() {
|
||||
const Sortable = (await import("../../../resources/sortable")).default;
|
||||
this._sortable = new Sortable(
|
||||
this.shadowRoot!.querySelector(".entities")!,
|
||||
{
|
||||
animation: 150,
|
||||
fallbackClass: "sortable-fallback",
|
||||
handle: ".handle",
|
||||
dataIdAttr: "data-entity-id",
|
||||
onChoose: (evt: SortableEvent) => {
|
||||
(evt.item as any).placeholder =
|
||||
document.createComment("sort-placeholder");
|
||||
evt.item.after((evt.item as any).placeholder);
|
||||
},
|
||||
onEnd: (evt: SortableEvent) => {
|
||||
// put back in original location
|
||||
if ((evt.item as any).placeholder) {
|
||||
(evt.item as any).placeholder.replaceWith(evt.item);
|
||||
delete (evt.item as any).placeholder;
|
||||
}
|
||||
this._entityMoved(evt);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _destroySortable() {
|
||||
this._sortable?.destroy();
|
||||
this._sortable = undefined;
|
||||
}
|
||||
|
||||
private async _addEntity(ev: CustomEvent): Promise<void> {
|
||||
const value = ev.detail.value;
|
||||
if (value === "") {
|
||||
@ -132,14 +90,13 @@ export class HuiEntityEditor extends LitElement {
|
||||
fireEvent(this, "entities-changed", { entities: newConfigEntities });
|
||||
}
|
||||
|
||||
private _entityMoved(ev: SortableEvent): void {
|
||||
if (ev.oldIndex === ev.newIndex) {
|
||||
return;
|
||||
}
|
||||
private _entityMoved(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
|
||||
const newEntities = this.entities!.concat();
|
||||
|
||||
newEntities.splice(ev.newIndex!, 0, newEntities.splice(ev.oldIndex!, 1)[0]);
|
||||
newEntities.splice(newIndex, 0, newEntities.splice(oldIndex, 1)[0]);
|
||||
|
||||
fireEvent(this, "entities-changed", { entities: newEntities });
|
||||
}
|
||||
@ -162,9 +119,7 @@ export class HuiEntityEditor extends LitElement {
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
sortableStyles,
|
||||
css`
|
||||
return css`
|
||||
ha-entity-picker {
|
||||
margin-top: 8px;
|
||||
}
|
||||
@ -193,8 +148,7 @@ export class HuiEntityEditor extends LitElement {
|
||||
.entity ha-entity-picker {
|
||||
flex-grow: 1;
|
||||
}
|
||||
`,
|
||||
];
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,13 +3,13 @@ import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import type { SortableEvent } from "sortablejs";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
import "../../../../components/entity/ha-entity-picker";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-list-item";
|
||||
import "../../../../components/ha-sortable";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import {
|
||||
CUSTOM_TYPE_PREFIX,
|
||||
@ -18,8 +18,6 @@ import {
|
||||
isCustomType,
|
||||
stripCustomPrefix,
|
||||
} from "../../../../data/lovelace_custom_cards";
|
||||
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
||||
import type { SortableInstance } from "../../../../resources/sortable";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { supportsAlarmModesCardFeature } from "../../card-features/hui-alarm-modes-card-feature";
|
||||
import { supportsClimateFanModesCardFeature } from "../../card-features/hui-climate-fan-modes-card-feature";
|
||||
@ -39,11 +37,11 @@ import { supportsNumericInputCardFeature } from "../../card-features/hui-numeric
|
||||
import { supportsSelectOptionsCardFeature } from "../../card-features/hui-select-options-card-feature";
|
||||
import { supportsTargetHumidityCardFeature } from "../../card-features/hui-target-humidity-card-feature";
|
||||
import { supportsTargetTemperatureCardFeature } from "../../card-features/hui-target-temperature-card-feature";
|
||||
import { supportsUpdateActionsCardFeature } from "../../card-features/hui-update-actions-card-feature";
|
||||
import { supportsVacuumCommandsCardFeature } from "../../card-features/hui-vacuum-commands-card-feature";
|
||||
import { supportsWaterHeaterOperationModesCardFeature } from "../../card-features/hui-water-heater-operation-modes-card-feature";
|
||||
import { LovelaceCardFeatureConfig } from "../../card-features/types";
|
||||
import { getCardFeatureElementClass } from "../../create-element/create-card-feature-element";
|
||||
import { supportsUpdateActionsCardFeature } from "../../card-features/hui-update-actions-card-feature";
|
||||
|
||||
export type FeatureType = LovelaceCardFeatureConfig["type"];
|
||||
type SupportsFeature = (stateObj: HassEntity) => boolean;
|
||||
@ -149,13 +147,6 @@ export class HuiCardFeaturesEditor extends LitElement {
|
||||
|
||||
private _featuresKeys = new WeakMap<LovelaceCardFeatureConfig, string>();
|
||||
|
||||
private _sortable?: SortableInstance;
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._destroySortable();
|
||||
}
|
||||
|
||||
private _supportsFeatureType(type: string): boolean {
|
||||
if (!this.stateObj) return false;
|
||||
|
||||
@ -205,10 +196,6 @@ export class HuiCardFeaturesEditor extends LitElement {
|
||||
return this._featuresKeys.get(feature)!;
|
||||
}
|
||||
|
||||
protected firstUpdated() {
|
||||
this._createSortable();
|
||||
}
|
||||
|
||||
private _getSupportedFeaturesType() {
|
||||
const featuresTypes = UI_FEATURE_TYPES.filter(
|
||||
(type) => !this.featuresTypes || this.featuresTypes.includes(type)
|
||||
@ -249,6 +236,10 @@ export class HuiCardFeaturesEditor extends LitElement {
|
||||
</ha-alert>
|
||||
`
|
||||
: nothing}
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
@item-moved=${this._featureMoved}
|
||||
>
|
||||
<div class="features">
|
||||
${repeat(
|
||||
this.features,
|
||||
@ -304,6 +295,7 @@ export class HuiCardFeaturesEditor extends LitElement {
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</ha-sortable>
|
||||
${supportedFeaturesType.length > 0
|
||||
? html`
|
||||
<ha-button-menu
|
||||
@ -345,36 +337,6 @@ export class HuiCardFeaturesEditor extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private async _createSortable() {
|
||||
const Sortable = (await import("../../../../resources/sortable")).default;
|
||||
this._sortable = new Sortable(
|
||||
this.shadowRoot!.querySelector(".features")!,
|
||||
{
|
||||
animation: 150,
|
||||
fallbackClass: "sortable-fallback",
|
||||
handle: ".handle",
|
||||
onChoose: (evt: SortableEvent) => {
|
||||
(evt.item as any).placeholder =
|
||||
document.createComment("sort-placeholder");
|
||||
evt.item.after((evt.item as any).placeholder);
|
||||
},
|
||||
onEnd: (evt: SortableEvent) => {
|
||||
// put back in original location
|
||||
if ((evt.item as any).placeholder) {
|
||||
(evt.item as any).placeholder.replaceWith(evt.item);
|
||||
delete (evt.item as any).placeholder;
|
||||
}
|
||||
this._rowMoved(evt);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _destroySortable() {
|
||||
this._sortable?.destroy();
|
||||
this._sortable = undefined;
|
||||
}
|
||||
|
||||
private async _addFeature(ev: CustomEvent): Promise<void> {
|
||||
const index = ev.detail.index as number;
|
||||
|
||||
@ -395,14 +357,13 @@ export class HuiCardFeaturesEditor extends LitElement {
|
||||
fireEvent(this, "features-changed", { features: newConfigFeature });
|
||||
}
|
||||
|
||||
private _rowMoved(ev: SortableEvent): void {
|
||||
if (ev.oldIndex === ev.newIndex) {
|
||||
return;
|
||||
}
|
||||
private _featureMoved(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
|
||||
const newFeatures = this.features!.concat();
|
||||
|
||||
newFeatures.splice(ev.newIndex!, 0, newFeatures.splice(ev.oldIndex!, 1)[0]);
|
||||
newFeatures.splice(newIndex, 0, newFeatures.splice(oldIndex, 1)[0]);
|
||||
|
||||
fireEvent(this, "features-changed", { features: newFeatures });
|
||||
}
|
||||
@ -428,9 +389,7 @@ export class HuiCardFeaturesEditor extends LitElement {
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
sortableStyles,
|
||||
css`
|
||||
return css`
|
||||
:host {
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
@ -460,9 +419,9 @@ export class HuiCardFeaturesEditor extends LitElement {
|
||||
align-items: center;
|
||||
}
|
||||
.feature .handle {
|
||||
padding-right: 8px;
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
cursor: grab;
|
||||
padding-right: 8px;
|
||||
padding-inline-end: 8px;
|
||||
padding-inline-start: initial;
|
||||
direction: var(--direction);
|
||||
@ -499,8 +458,7 @@ export class HuiCardFeaturesEditor extends LitElement {
|
||||
li[divider] {
|
||||
border-bottom-color: var(--divider-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,13 @@
|
||||
import { mdiClose, mdiDrag, mdiPencil } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import type { SortableEvent } from "sortablejs";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/entity/ha-entity-picker";
|
||||
import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-sortable";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { sortableStyles } from "../../../resources/ha-sortable-style";
|
||||
import type { SortableInstance } from "../../../resources/sortable";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { EntityConfig, LovelaceRowConfig } from "../entity-rows/types";
|
||||
|
||||
@ -31,13 +29,6 @@ export class HuiEntitiesCardRowEditor extends LitElement {
|
||||
|
||||
private _entityKeys = new WeakMap<LovelaceRowConfig, string>();
|
||||
|
||||
private _sortable?: SortableInstance;
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._destroySortable();
|
||||
}
|
||||
|
||||
private _getKey(action: LovelaceRowConfig) {
|
||||
if (!this._entityKeys.has(action)) {
|
||||
this._entityKeys.set(action, Math.random().toString());
|
||||
@ -60,6 +51,7 @@ export class HuiEntitiesCardRowEditor extends LitElement {
|
||||
"ui.panel.lovelace.editor.card.config.required"
|
||||
)})`}
|
||||
</h3>
|
||||
<ha-sortable handle-selector=".handle" @item-moved=${this._rowMoved}>
|
||||
<div class="entities">
|
||||
${repeat(
|
||||
this.entities,
|
||||
@ -118,6 +110,7 @@ export class HuiEntitiesCardRowEditor extends LitElement {
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</ha-sortable>
|
||||
<ha-entity-picker
|
||||
class="add-entity"
|
||||
.hass=${this.hass}
|
||||
@ -126,40 +119,6 @@ export class HuiEntitiesCardRowEditor extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this._createSortable();
|
||||
}
|
||||
|
||||
private async _createSortable() {
|
||||
const Sortable = (await import("../../../resources/sortable")).default;
|
||||
this._sortable = new Sortable(
|
||||
this.shadowRoot!.querySelector(".entities")!,
|
||||
{
|
||||
animation: 150,
|
||||
fallbackClass: "sortable-fallback",
|
||||
handle: ".handle",
|
||||
onChoose: (evt: SortableEvent) => {
|
||||
(evt.item as any).placeholder =
|
||||
document.createComment("sort-placeholder");
|
||||
evt.item.after((evt.item as any).placeholder);
|
||||
},
|
||||
onEnd: (evt: SortableEvent) => {
|
||||
// put back in original location
|
||||
if ((evt.item as any).placeholder) {
|
||||
(evt.item as any).placeholder.replaceWith(evt.item);
|
||||
delete (evt.item as any).placeholder;
|
||||
}
|
||||
this._rowMoved(evt);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _destroySortable() {
|
||||
this._sortable?.destroy();
|
||||
this._sortable = undefined;
|
||||
}
|
||||
|
||||
private async _addEntity(ev: CustomEvent): Promise<void> {
|
||||
const value = ev.detail.value;
|
||||
if (value === "") {
|
||||
@ -172,14 +131,13 @@ export class HuiEntitiesCardRowEditor extends LitElement {
|
||||
fireEvent(this, "entities-changed", { entities: newConfigEntities });
|
||||
}
|
||||
|
||||
private _rowMoved(ev: SortableEvent): void {
|
||||
if (ev.oldIndex === ev.newIndex) {
|
||||
return;
|
||||
}
|
||||
private _rowMoved(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
|
||||
const newEntities = this.entities!.concat();
|
||||
|
||||
newEntities.splice(ev.newIndex!, 0, newEntities.splice(ev.oldIndex!, 1)[0]);
|
||||
newEntities.splice(newIndex, 0, newEntities.splice(oldIndex, 1)[0]);
|
||||
|
||||
fireEvent(this, "entities-changed", { entities: newEntities });
|
||||
}
|
||||
@ -222,9 +180,7 @@ export class HuiEntitiesCardRowEditor extends LitElement {
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
sortableStyles,
|
||||
css`
|
||||
return css`
|
||||
ha-entity-picker {
|
||||
margin-top: 8px;
|
||||
}
|
||||
@ -281,8 +237,7 @@ export class HuiEntitiesCardRowEditor extends LitElement {
|
||||
font-size: 12px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { css } from "lit";
|
||||
|
||||
export const sortableStyles = css`
|
||||
#sortable a:nth-of-type(2n) paper-icon-item {
|
||||
export const sidebarEditStyle = css`
|
||||
.reorder-list a:nth-of-type(2n) paper-icon-item {
|
||||
animation-name: keyframes1;
|
||||
animation-iteration-count: infinite;
|
||||
transform-origin: 50% 10%;
|
||||
@ -9,7 +9,7 @@ export const sortableStyles = css`
|
||||
animation-duration: 0.25s;
|
||||
}
|
||||
|
||||
#sortable a:nth-of-type(2n-1) paper-icon-item {
|
||||
.reorder-list a:nth-of-type(2n-1) paper-icon-item {
|
||||
animation-name: keyframes2;
|
||||
animation-iteration-count: infinite;
|
||||
animation-direction: alternate;
|
||||
@ -18,12 +18,12 @@ export const sortableStyles = css`
|
||||
animation-duration: 0.33s;
|
||||
}
|
||||
|
||||
#sortable a {
|
||||
.reorder-list a {
|
||||
height: 48px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#sortable {
|
||||
.reorder-list {
|
||||
outline: none;
|
||||
display: block !important;
|
||||
}
|
||||
@ -32,26 +32,6 @@ export const sortableStyles = css`
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.sortable-fallback {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.sortable-ghost {
|
||||
border: 2px solid var(--primary-color);
|
||||
background: rgba(var(--rgb-primary-color), 0.25);
|
||||
border-radius: 4px;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.sortable-drag {
|
||||
border-radius: 4px;
|
||||
opacity: 1;
|
||||
background: var(--card-background-color);
|
||||
box-shadow: 0px 4px 8px 3px #00000026;
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
@keyframes keyframes1 {
|
||||
0% {
|
||||
transform: rotate(-1deg);
|
Loading…
x
Reference in New Issue
Block a user