Add toolbar to YAML/template editors (#26580)
parent
88ac56ac0b
commit
7f88d863e9
|
@ -5,9 +5,16 @@ import type {
|
||||||
CompletionResult,
|
CompletionResult,
|
||||||
CompletionSource,
|
CompletionSource,
|
||||||
} from "@codemirror/autocomplete";
|
} from "@codemirror/autocomplete";
|
||||||
|
import { undo, undoDepth, redo, redoDepth } from "@codemirror/commands";
|
||||||
import type { Extension, TransactionSpec } from "@codemirror/state";
|
import type { Extension, TransactionSpec } from "@codemirror/state";
|
||||||
import type { EditorView, KeyBinding, ViewUpdate } from "@codemirror/view";
|
import type { EditorView, KeyBinding, ViewUpdate } from "@codemirror/view";
|
||||||
import { mdiArrowExpand, mdiArrowCollapse } from "@mdi/js";
|
import {
|
||||||
|
mdiArrowExpand,
|
||||||
|
mdiArrowCollapse,
|
||||||
|
mdiContentCopy,
|
||||||
|
mdiUndo,
|
||||||
|
mdiRedo,
|
||||||
|
} from "@mdi/js";
|
||||||
import type { HassEntities } from "home-assistant-js-websocket";
|
import type { HassEntities } from "home-assistant-js-websocket";
|
||||||
import type { PropertyValues } from "lit";
|
import type { PropertyValues } from "lit";
|
||||||
import { css, ReactiveElement, html, render } from "lit";
|
import { css, ReactiveElement, html, render } from "lit";
|
||||||
|
@ -16,11 +23,14 @@ import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||||
import { getEntityContext } from "../common/entity/context/get_entity_context";
|
import { getEntityContext } from "../common/entity/context/get_entity_context";
|
||||||
|
import { copyToClipboard } from "../common/util/copy-clipboard";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
|
import { showToast } from "../util/toast";
|
||||||
|
import "./ha-code-editor-completion-items";
|
||||||
import type { CompletionItem } from "./ha-code-editor-completion-items";
|
import type { CompletionItem } from "./ha-code-editor-completion-items";
|
||||||
import "./ha-icon";
|
import "./ha-icon";
|
||||||
import "./ha-icon-button";
|
import "./ha-icon-button-toolbar";
|
||||||
import "./ha-code-editor-completion-items";
|
import type { HaIconButtonToolbar } from "./ha-icon-button-toolbar";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
|
@ -68,13 +78,24 @@ export class HaCodeEditor extends ReactiveElement {
|
||||||
@property({ type: Boolean, attribute: "disable-fullscreen" })
|
@property({ type: Boolean, attribute: "disable-fullscreen" })
|
||||||
public disableFullscreen = false;
|
public disableFullscreen = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean, attribute: "has-toolbar" })
|
||||||
|
public hasToolbar = true;
|
||||||
|
|
||||||
@state() private _value = "";
|
@state() private _value = "";
|
||||||
|
|
||||||
@state() private _isFullscreen = false;
|
@state() private _isFullscreen = false;
|
||||||
|
|
||||||
|
@state() private _canUndo = false;
|
||||||
|
|
||||||
|
@state() private _canRedo = false;
|
||||||
|
|
||||||
|
@state() private _canCopy = false;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||||
private _loadedCodeMirror?: typeof import("../resources/codemirror");
|
private _loadedCodeMirror?: typeof import("../resources/codemirror");
|
||||||
|
|
||||||
|
private _editorToolbar?: HaIconButtonToolbar;
|
||||||
|
|
||||||
private _iconList?: Completion[];
|
private _iconList?: Completion[];
|
||||||
|
|
||||||
public set value(value: string) {
|
public set value(value: string) {
|
||||||
|
@ -119,9 +140,7 @@ export class HaCodeEditor extends ReactiveElement {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
this.removeEventListener("keydown", stopPropagation);
|
this.removeEventListener("keydown", stopPropagation);
|
||||||
this.removeEventListener("keydown", this._handleKeyDown);
|
this.removeEventListener("keydown", this._handleKeyDown);
|
||||||
if (this._isFullscreen) {
|
this._updateFullscreenState(false);
|
||||||
this._toggleFullscreen();
|
|
||||||
}
|
|
||||||
this.updateComplete.then(() => {
|
this.updateComplete.then(() => {
|
||||||
this.codemirror!.destroy();
|
this.codemirror!.destroy();
|
||||||
delete this.codemirror;
|
delete this.codemirror;
|
||||||
|
@ -157,6 +176,7 @@ export class HaCodeEditor extends ReactiveElement {
|
||||||
this._loadedCodeMirror!.EditorView!.editable.of(!this.readOnly)
|
this._loadedCodeMirror!.EditorView!.editable.of(!this.readOnly)
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
this._updateToolbarButtons();
|
||||||
}
|
}
|
||||||
if (changedProps.has("linewrap")) {
|
if (changedProps.has("linewrap")) {
|
||||||
transactions.push({
|
transactions.push({
|
||||||
|
@ -177,14 +197,25 @@ export class HaCodeEditor extends ReactiveElement {
|
||||||
if (transactions.length > 0) {
|
if (transactions.length > 0) {
|
||||||
this.codemirror.dispatch(...transactions);
|
this.codemirror.dispatch(...transactions);
|
||||||
}
|
}
|
||||||
|
if (changedProps.has("hasToolbar")) {
|
||||||
|
this._updateToolbar();
|
||||||
|
}
|
||||||
if (changedProps.has("error")) {
|
if (changedProps.has("error")) {
|
||||||
this.classList.toggle("error-state", this.error);
|
this.classList.toggle("error-state", this.error);
|
||||||
}
|
}
|
||||||
if (changedProps.has("_isFullscreen")) {
|
if (changedProps.has("_isFullscreen")) {
|
||||||
this.classList.toggle("fullscreen", this._isFullscreen);
|
this.classList.toggle("fullscreen", this._isFullscreen);
|
||||||
|
this._updateToolbarButtons();
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
changedProps.has("_canCopy") ||
|
||||||
|
changedProps.has("_canUndo") ||
|
||||||
|
changedProps.has("_canRedo")
|
||||||
|
) {
|
||||||
|
this._updateToolbarButtons();
|
||||||
}
|
}
|
||||||
if (changedProps.has("disableFullscreen")) {
|
if (changedProps.has("disableFullscreen")) {
|
||||||
this._updateFullscreenButton();
|
this._updateFullscreenState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,6 +284,7 @@ export class HaCodeEditor extends ReactiveElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create the code editor
|
||||||
this.codemirror = new this._loadedCodeMirror.EditorView({
|
this.codemirror = new this._loadedCodeMirror.EditorView({
|
||||||
state: this._loadedCodeMirror.EditorState.create({
|
state: this._loadedCodeMirror.EditorState.create({
|
||||||
doc: this._value,
|
doc: this._value,
|
||||||
|
@ -260,71 +292,160 @@ export class HaCodeEditor extends ReactiveElement {
|
||||||
}),
|
}),
|
||||||
parent: this.renderRoot,
|
parent: this.renderRoot,
|
||||||
});
|
});
|
||||||
|
this._canCopy = this._value?.length > 0;
|
||||||
|
|
||||||
this._updateFullscreenButton();
|
// Update the toolbar. Creating it if required
|
||||||
|
this._updateToolbar();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _updateFullscreenButton() {
|
private _fullscreenLabel(): string {
|
||||||
const existingButton = this.renderRoot.querySelector(".fullscreen-button");
|
if (this._isFullscreen)
|
||||||
|
return (
|
||||||
|
this.hass?.localize("ui.components.yaml-editor.exit_fullscreen") ||
|
||||||
|
"Exit fullscreen"
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
this.hass?.localize("ui.components.yaml-editor.enter_fullscreen") ||
|
||||||
|
"Enter fullscreen"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.disableFullscreen) {
|
private _fullscreenIcon(): string {
|
||||||
// Remove button if it exists and fullscreen is disabled
|
return this._isFullscreen ? mdiArrowCollapse : mdiArrowExpand;
|
||||||
if (existingButton) {
|
}
|
||||||
existingButton.remove();
|
|
||||||
}
|
private _createEditorToolbar(): HaIconButtonToolbar {
|
||||||
// Exit fullscreen if currently in fullscreen mode
|
// Create the editor toolbar element
|
||||||
if (this._isFullscreen) {
|
const editorToolbar = document.createElement("ha-icon-button-toolbar");
|
||||||
this._isFullscreen = false;
|
editorToolbar.classList.add("code-editor-toolbar");
|
||||||
}
|
editorToolbar.items = [];
|
||||||
|
return editorToolbar;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _updateToolbar() {
|
||||||
|
// Show/Hide the toolbar if we have one.
|
||||||
|
this.classList.toggle("hasToolbar", this.hasToolbar);
|
||||||
|
|
||||||
|
// Update fullscreen state. Handles toolbar and fullscreen mode being disabled.
|
||||||
|
this._updateFullscreenState();
|
||||||
|
|
||||||
|
// If we don't have a toolbar, nothing to update
|
||||||
|
if (!this.hasToolbar) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create button if it doesn't exist
|
// If we don't yet have the toolbar, create it.
|
||||||
if (!existingButton) {
|
if (!this._editorToolbar) {
|
||||||
const button = document.createElement("ha-icon-button");
|
this._editorToolbar = this._createEditorToolbar();
|
||||||
(button as any).path = this._isFullscreen
|
|
||||||
? mdiArrowCollapse
|
|
||||||
: mdiArrowExpand;
|
|
||||||
button.setAttribute(
|
|
||||||
"label",
|
|
||||||
this._isFullscreen ? "Exit fullscreen" : "Enter fullscreen"
|
|
||||||
);
|
|
||||||
button.classList.add("fullscreen-button");
|
|
||||||
// Use bound method to ensure proper this context
|
|
||||||
button.addEventListener("click", this._handleFullscreenClick);
|
|
||||||
this.renderRoot.appendChild(button);
|
|
||||||
} else {
|
|
||||||
// Update existing button
|
|
||||||
(existingButton as any).path = this._isFullscreen
|
|
||||||
? mdiArrowCollapse
|
|
||||||
: mdiArrowExpand;
|
|
||||||
existingButton.setAttribute(
|
|
||||||
"label",
|
|
||||||
this._isFullscreen ? "Exit fullscreen" : "Enter fullscreen"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure all toolbar buttons are correctly configured.
|
||||||
|
this._updateToolbarButtons();
|
||||||
|
|
||||||
|
// Render the toolbar. This must be placed as a child of the code
|
||||||
|
// mirror element to ensure it doesn't affect the positioning and
|
||||||
|
// size of codemirror.
|
||||||
|
this.codemirror?.dom.appendChild(this._editorToolbar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _updateToolbarButtons() {
|
||||||
|
// Re-render all toolbar items.
|
||||||
|
if (!this._editorToolbar) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._editorToolbar.items = [
|
||||||
|
{
|
||||||
|
id: "undo",
|
||||||
|
disabled: !this._canUndo,
|
||||||
|
label: this.hass?.localize("ui.common.undo") || "Undo",
|
||||||
|
path: mdiUndo,
|
||||||
|
action: (e: Event) => this._handleUndoClick(e),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "redo",
|
||||||
|
disabled: !this._canRedo,
|
||||||
|
label: this.hass?.localize("ui.common.redo") || "Redo",
|
||||||
|
path: mdiRedo,
|
||||||
|
action: (e: Event) => this._handleRedoClick(e),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "copy",
|
||||||
|
disabled: !this._canCopy,
|
||||||
|
label:
|
||||||
|
this.hass?.localize("ui.components.yaml-editor.copy_to_clipboard") ||
|
||||||
|
"Copy to Clipboard",
|
||||||
|
path: mdiContentCopy,
|
||||||
|
action: (e: Event) => this._handleClipboardClick(e),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "fullscreen",
|
||||||
|
disabled: this.disableFullscreen,
|
||||||
|
label: this._fullscreenLabel(),
|
||||||
|
path: this._fullscreenIcon(),
|
||||||
|
action: (e: Event) => this._handleFullscreenClick(e),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private _updateFullscreenState(
|
||||||
|
fullscreen: boolean = this._isFullscreen
|
||||||
|
): boolean {
|
||||||
|
// Update the current fullscreen state based on selected value. If fullscreen
|
||||||
|
// is disabled, or we have no toolbar, ensure we are not in fullscreen mode.
|
||||||
|
this._isFullscreen =
|
||||||
|
fullscreen && !this.disableFullscreen && this.hasToolbar;
|
||||||
|
// Return whether successfully in requested state
|
||||||
|
return this._isFullscreen === fullscreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleClipboardClick = async (e: Event) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
if (this.value) {
|
||||||
|
await copyToClipboard(this.value);
|
||||||
|
showToast(this, {
|
||||||
|
message:
|
||||||
|
this.hass?.localize("ui.common.copied_clipboard") ||
|
||||||
|
"Copied to clipboard",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private _handleUndoClick = (e: Event) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!this.codemirror) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
undo(this.codemirror);
|
||||||
|
};
|
||||||
|
|
||||||
|
private _handleRedoClick = (e: Event) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!this.codemirror) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
redo(this.codemirror);
|
||||||
|
};
|
||||||
|
|
||||||
private _handleFullscreenClick = (e: Event) => {
|
private _handleFullscreenClick = (e: Event) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this._toggleFullscreen();
|
this._updateFullscreenState(!this._isFullscreen);
|
||||||
};
|
};
|
||||||
|
|
||||||
private _toggleFullscreen() {
|
|
||||||
this._isFullscreen = !this._isFullscreen;
|
|
||||||
this._updateFullscreenButton();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleKeyDown = (e: KeyboardEvent) => {
|
private _handleKeyDown = (e: KeyboardEvent) => {
|
||||||
if (this._isFullscreen && e.key === "Escape") {
|
if (
|
||||||
|
(e.key === "Escape" &&
|
||||||
|
this._isFullscreen &&
|
||||||
|
this._updateFullscreenState(false)) ||
|
||||||
|
(e.key === "F11" && this._updateFullscreenState(true))
|
||||||
|
) {
|
||||||
|
// If we successfully performed the action, stop it propagating further.
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this._toggleFullscreen();
|
|
||||||
} else if (e.key === "F11" && !this.disableFullscreen) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
this._toggleFullscreen();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -592,10 +713,13 @@ export class HaCodeEditor extends ReactiveElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onUpdate = (update: ViewUpdate): void => {
|
private _onUpdate = (update: ViewUpdate): void => {
|
||||||
|
this._canUndo = !this.readOnly && undoDepth(update.state) > 0;
|
||||||
|
this._canRedo = !this.readOnly && redoDepth(update.state) > 0;
|
||||||
if (!update.docChanged) {
|
if (!update.docChanged) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._value = update.state.doc.toString();
|
this._value = update.state.doc.toString();
|
||||||
|
this._canCopy = this._value?.length > 0;
|
||||||
fireEvent(this, "value-changed", { value: this._value });
|
fireEvent(this, "value-changed", { value: this._value });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -614,39 +738,33 @@ export class HaCodeEditor extends ReactiveElement {
|
||||||
:host {
|
:host {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: block;
|
display: block;
|
||||||
|
--code-editor-toolbar-height: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host(.error-state) .cm-gutters {
|
:host(.error-state) .cm-gutters {
|
||||||
border-color: var(--error-state-color, red);
|
border-color: var(--error-state-color, var(--error-color)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullscreen-button {
|
:host(.hasToolbar) .cm-gutters {
|
||||||
position: absolute;
|
padding-top: 0;
|
||||||
top: 8px;
|
|
||||||
right: 8px;
|
|
||||||
z-index: 1;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
background-color: var(--secondary-background-color);
|
|
||||||
border-radius: 50%;
|
|
||||||
opacity: 0.9;
|
|
||||||
transition: opacity 0.2s;
|
|
||||||
--mdc-icon-button-size: 32px;
|
|
||||||
--mdc-icon-size: 18px;
|
|
||||||
/* Ensure button is clickable on iOS */
|
|
||||||
cursor: pointer;
|
|
||||||
-webkit-tap-highlight-color: transparent;
|
|
||||||
touch-action: manipulation;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullscreen-button:hover,
|
:host(.hasToolbar) .cm-focused .cm-gutters {
|
||||||
.fullscreen-button:active {
|
padding-top: 1px;
|
||||||
opacity: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (hover: none) {
|
:host(.error-state) .cm-content {
|
||||||
.fullscreen-button {
|
border-color: var(--error-state-color, var(--error-color)) !important;
|
||||||
opacity: 0.8;
|
}
|
||||||
}
|
|
||||||
|
:host(.hasToolbar) .cm-content {
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
:host(.hasToolbar) .cm-focused .cm-content {
|
||||||
|
border-top: 2px solid var(--primary-color);
|
||||||
|
padding-top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host(.fullscreen) {
|
:host(.fullscreen) {
|
||||||
|
@ -655,7 +773,7 @@ export class HaCodeEditor extends ReactiveElement {
|
||||||
left: 8px !important;
|
left: 8px !important;
|
||||||
right: 8px !important;
|
right: 8px !important;
|
||||||
bottom: 8px !important;
|
bottom: 8px !important;
|
||||||
z-index: 9999 !important;
|
z-index: 6;
|
||||||
border-radius: 12px !important;
|
border-radius: 12px !important;
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3) !important;
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3) !important;
|
||||||
overflow: hidden !important;
|
overflow: hidden !important;
|
||||||
|
@ -672,15 +790,28 @@ export class HaCodeEditor extends ReactiveElement {
|
||||||
display: block !important;
|
display: block !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host(.hasToolbar) .cm-editor {
|
||||||
|
padding-top: var(--code-editor-toolbar-height);
|
||||||
|
}
|
||||||
|
|
||||||
:host(.fullscreen) .cm-editor {
|
:host(.fullscreen) .cm-editor {
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
max-height: 100% !important;
|
max-height: 100% !important;
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host(.fullscreen) .fullscreen-button {
|
:host(:not(.hasToolbar)) .code-editor-toolbar {
|
||||||
top: calc(var(--safe-area-inset-top, 0px) + 8px);
|
display: none !important;
|
||||||
right: calc(var(--safe-area-inset-right, 0px) + 8px);
|
}
|
||||||
|
|
||||||
|
.code-editor-toolbar {
|
||||||
|
--icon-button-toolbar-height: var(--code-editor-toolbar-height);
|
||||||
|
--icon-button-toolbar-color: var(
|
||||||
|
--code-editor-gutter-color,
|
||||||
|
var(--secondary-background-color, whitesmoke)
|
||||||
|
);
|
||||||
|
border-top-left-radius: var(--ha-border-radius-sm);
|
||||||
|
border-top-right-radius: var(--ha-border-radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.completion-info {
|
.completion-info {
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
import type { TemplateResult } from "lit";
|
||||||
|
import { css, html, LitElement } from "lit";
|
||||||
|
import { customElement, property, queryAll } from "lit/decorators";
|
||||||
|
import "./ha-icon";
|
||||||
|
import "./ha-icon-button";
|
||||||
|
import type { HaIconButton } from "./ha-icon-button";
|
||||||
|
import "./ha-icon-button-group";
|
||||||
|
import "./ha-tooltip";
|
||||||
|
|
||||||
|
export interface HaIconButtonToolbarItem {
|
||||||
|
[key: string]: any;
|
||||||
|
path: string;
|
||||||
|
label: string;
|
||||||
|
id?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
tooltip?: string;
|
||||||
|
action?: (e: Event) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("ha-icon-button-toolbar")
|
||||||
|
export class HaIconButtonToolbar extends LitElement {
|
||||||
|
@property({ type: Array, attribute: false })
|
||||||
|
public items: (HaIconButtonToolbarItem | string)[] = [];
|
||||||
|
|
||||||
|
@queryAll("ha-icon-button") private _buttons?: HaIconButton[];
|
||||||
|
|
||||||
|
// Returns all toolbar buttons, or undefined if there are none.
|
||||||
|
// Optionally returns only those with matching selector.
|
||||||
|
public findToolbarButtons(selector = ""): HaIconButton[] | undefined {
|
||||||
|
// Search for all toolbar buttons
|
||||||
|
const toolbarButtons = this._buttons?.filter((button) =>
|
||||||
|
button.classList.contains("icon-toolbar-button")
|
||||||
|
);
|
||||||
|
if (!toolbarButtons || !toolbarButtons.length) return undefined;
|
||||||
|
if (!selector.length) return toolbarButtons;
|
||||||
|
// Filter by user class if provided
|
||||||
|
const classButtons = toolbarButtons.filter((button) =>
|
||||||
|
button.querySelector(selector)
|
||||||
|
);
|
||||||
|
return classButtons.length ? classButtons : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a toolbar button based on the provided id.
|
||||||
|
// Will return undefined if not found.
|
||||||
|
public findToolbarButtonById(id): HaIconButton | undefined {
|
||||||
|
// Find the specified id
|
||||||
|
const element = this.shadowRoot?.getElementById(id);
|
||||||
|
if (!element || element.localName !== "ha-icon-button") return undefined;
|
||||||
|
return element as HaIconButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<ha-icon-button-group class="icon-toolbar-buttongroup">
|
||||||
|
${this.items.map((item) =>
|
||||||
|
typeof item === "string"
|
||||||
|
? html`<div class="icon-toolbar-divider" role="separator"></div>`
|
||||||
|
: html`<ha-tooltip
|
||||||
|
.disabled=${!item.tooltip}
|
||||||
|
.for=${item.id ?? "icon-button-" + item.label}
|
||||||
|
>${item.tooltip ?? ""}</ha-tooltip
|
||||||
|
>
|
||||||
|
<ha-icon-button
|
||||||
|
class="icon-toolbar-button"
|
||||||
|
.id=${item.id ?? "icon-button-" + item.label}
|
||||||
|
@click=${item.action}
|
||||||
|
.label=${item.label}
|
||||||
|
.path=${item.path}
|
||||||
|
.disabled=${item.disabled ?? false}
|
||||||
|
></ha-icon-button>`
|
||||||
|
)}
|
||||||
|
</ha-icon-button-group>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
:host {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
background-color: var(
|
||||||
|
--icon-button-toolbar-color,
|
||||||
|
var(--secondary-background-color, whitesmoke)
|
||||||
|
);
|
||||||
|
--icon-button-toolbar-height: 32px;
|
||||||
|
--icon-button-toolbar-button: calc(
|
||||||
|
var(--icon-button-toolbar-height) - 4px
|
||||||
|
);
|
||||||
|
--icon-button-toolbar-icon: calc(
|
||||||
|
var(--icon-button-toolbar-height) - 10px
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-toolbar-divider {
|
||||||
|
height: var(--icon-button-toolbar-icon);
|
||||||
|
margin: 0px 4px;
|
||||||
|
border: 0.5px solid
|
||||||
|
var(--divider-color, var(--secondary-text-color, transparent));
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-toolbar-buttongroup {
|
||||||
|
background-color: transparent;
|
||||||
|
padding-right: 4px;
|
||||||
|
height: var(--icon-button-toolbar-height);
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-toolbar-button {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
--mdc-icon-button-size: var(--icon-button-toolbar-button);
|
||||||
|
--mdc-icon-size: var(--icon-button-toolbar-icon);
|
||||||
|
/* Ensure button is clickable on iOS */
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
touch-action: manipulation;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-icon-button-toolbar": HaIconButtonToolbar;
|
||||||
|
}
|
||||||
|
}
|
|
@ -593,7 +593,6 @@ export class HaAutomationEditor extends UndoRedoMixin<
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
<ha-yaml-editor
|
<ha-yaml-editor
|
||||||
copy-clipboard
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.defaultValue=${this._preprocessYaml()}
|
.defaultValue=${this._preprocessYaml()}
|
||||||
.readOnly=${this._readOnly}
|
.readOnly=${this._readOnly}
|
||||||
|
|
|
@ -500,7 +500,6 @@ export class HaScriptEditor extends UndoRedoMixin<
|
||||||
`
|
`
|
||||||
: this._mode === "yaml"
|
: this._mode === "yaml"
|
||||||
? html`<ha-yaml-editor
|
? html`<ha-yaml-editor
|
||||||
copy-clipboard
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.defaultValue=${this._preprocessYaml()}
|
.defaultValue=${this._preprocessYaml()}
|
||||||
.readOnly=${this._readOnly}
|
.readOnly=${this._readOnly}
|
||||||
|
|
|
@ -215,7 +215,6 @@ class HaPanelDevAction extends LitElement {
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<ha-yaml-editor
|
<ha-yaml-editor
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
copy-clipboard
|
|
||||||
read-only
|
read-only
|
||||||
auto-update
|
auto-update
|
||||||
has-extra-actions
|
has-extra-actions
|
||||||
|
|
|
@ -1219,7 +1219,9 @@
|
||||||
"yaml-editor": {
|
"yaml-editor": {
|
||||||
"copy_to_clipboard": "[%key:ui::panel::config::automation::editor::copy_to_clipboard%]",
|
"copy_to_clipboard": "[%key:ui::panel::config::automation::editor::copy_to_clipboard%]",
|
||||||
"error": "Error in parsing YAML: {reason}",
|
"error": "Error in parsing YAML: {reason}",
|
||||||
"error_location": "line: {line}, column: {column}"
|
"error_location": "line: {line}, column: {column}",
|
||||||
|
"enter_fullscreen": "Enter fullscreen",
|
||||||
|
"exit_fullscreen": "Exit fullscreen"
|
||||||
},
|
},
|
||||||
"state-content-picker": {
|
"state-content-picker": {
|
||||||
"state": "State",
|
"state": "State",
|
||||||
|
|
Loading…
Reference in New Issue