mirror of
				https://github.com/home-assistant/frontend.git
				synced 2025-11-04 00:19:47 +00:00 
			
		
		
		
	Compare commits
	
		
			20 Commits
		
	
	
		
			20251001.1
			...
			feature/ti
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					0ce17e3dea | ||
| 
						 | 
					ef9029829a | ||
| 
						 | 
					8bc53c61f2 | ||
| 
						 | 
					711dcdbff0 | ||
| 
						 | 
					6834e458d7 | ||
| 
						 | 
					d597239925 | ||
| 
						 | 
					004b3ce025 | ||
| 
						 | 
					43e7b55e99 | ||
| 
						 | 
					ea5fe14a64 | ||
| 
						 | 
					807fbf8bb6 | ||
| 
						 | 
					44cd425ce8 | ||
| 
						 | 
					af01f66329 | ||
| 
						 | 
					d5892b372c | ||
| 
						 | 
					8656df6129 | ||
| 
						 | 
					8c543ee67c | ||
| 
						 | 
					4789d8c793 | ||
| 
						 | 
					f08bbe7c1e | ||
| 
						 | 
					9f1ee988bc | ||
| 
						 | 
					eba0fa35d3 | ||
| 
						 | 
					5b8c5375b4 | 
							
								
								
									
										121
									
								
								src/components/ha-numeric-arrow-input.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								src/components/ha-numeric-arrow-input.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,121 @@
 | 
			
		||||
import { css, html, LitElement } from "lit";
 | 
			
		||||
import { customElement, property, query } from "lit/decorators";
 | 
			
		||||
import memoizeOne from "memoize-one";
 | 
			
		||||
import { mdiMinus, mdiPlus } from "@mdi/js";
 | 
			
		||||
import { fireEvent } from "../common/dom/fire_event";
 | 
			
		||||
import type { HaIconButton } from "./ha-icon-button";
 | 
			
		||||
import "./ha-textfield";
 | 
			
		||||
import "./ha-icon-button";
 | 
			
		||||
import { clampValue } from "../data/number";
 | 
			
		||||
 | 
			
		||||
@customElement("ha-numeric-arrow-input")
 | 
			
		||||
export class HaNumericArrowInput extends LitElement {
 | 
			
		||||
  @property({ attribute: false }) public disabled = false;
 | 
			
		||||
 | 
			
		||||
  @property({ attribute: false }) public required = false;
 | 
			
		||||
 | 
			
		||||
  @property({ attribute: false }) public min?: number;
 | 
			
		||||
 | 
			
		||||
  @property({ attribute: false }) public max?: number;
 | 
			
		||||
 | 
			
		||||
  @property({ attribute: false }) public step?: number;
 | 
			
		||||
 | 
			
		||||
  @property({ attribute: false }) public padStart?: number;
 | 
			
		||||
 | 
			
		||||
  @property({ attribute: false }) public labelUp = "Increase";
 | 
			
		||||
 | 
			
		||||
  @property({ attribute: false }) public labelDown = "Decrease";
 | 
			
		||||
 | 
			
		||||
  @property({ attribute: false }) public value = 0;
 | 
			
		||||
 | 
			
		||||
  @query("ha-icon-button[data-direction='up']")
 | 
			
		||||
  private _upButton!: HaIconButton;
 | 
			
		||||
 | 
			
		||||
  @query("ha-icon-button[data-direction='down']")
 | 
			
		||||
  private _downButton!: HaIconButton;
 | 
			
		||||
 | 
			
		||||
  private _paddedValue = memoizeOne((value: number, padStart?: number) =>
 | 
			
		||||
    value.toString().padStart(padStart ?? 0, "0")
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    return html`<div
 | 
			
		||||
      class="numeric-arrow-input-container"
 | 
			
		||||
      @keydown=${this._keyDown}
 | 
			
		||||
    >
 | 
			
		||||
      <ha-icon-button
 | 
			
		||||
        data-direction="up"
 | 
			
		||||
        .disabled=${this.disabled}
 | 
			
		||||
        .label=${this.labelUp}
 | 
			
		||||
        .path=${mdiPlus}
 | 
			
		||||
        @click=${this._up}
 | 
			
		||||
      ></ha-icon-button>
 | 
			
		||||
      <span class="numeric-arrow-input-value"
 | 
			
		||||
        >${this._paddedValue(this.value, this.padStart)}</span
 | 
			
		||||
      >
 | 
			
		||||
      <ha-icon-button
 | 
			
		||||
        data-direction="down"
 | 
			
		||||
        .disabled=${this.disabled}
 | 
			
		||||
        .label=${this.labelDown}
 | 
			
		||||
        .path=${mdiMinus}
 | 
			
		||||
        @click=${this._down}
 | 
			
		||||
      ></ha-icon-button>
 | 
			
		||||
    </div>`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _keyDown(ev: KeyboardEvent) {
 | 
			
		||||
    if (ev.key === "ArrowUp") {
 | 
			
		||||
      this._upButton.focus();
 | 
			
		||||
      this._up();
 | 
			
		||||
    }
 | 
			
		||||
    if (ev.key === "ArrowDown") {
 | 
			
		||||
      this._downButton.focus();
 | 
			
		||||
      this._down();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _up() {
 | 
			
		||||
    const newValue = this.value + (this.step ?? 1);
 | 
			
		||||
    fireEvent(
 | 
			
		||||
      this,
 | 
			
		||||
      "value-changed",
 | 
			
		||||
      clampValue({ value: newValue, min: this.min, max: this.max })
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _down() {
 | 
			
		||||
    const newValue = this.value - (this.step ?? 1);
 | 
			
		||||
    fireEvent(
 | 
			
		||||
      this,
 | 
			
		||||
      "value-changed",
 | 
			
		||||
      clampValue({ value: newValue, min: this.min, max: this.max })
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static styles = css`
 | 
			
		||||
    .numeric-arrow-input-container {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      flex-direction: column;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      justify-content: center;
 | 
			
		||||
      gap: 4px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .numeric-arrow-input-container ha-icon-button {
 | 
			
		||||
      --mdc-icon-button-size: 24px;
 | 
			
		||||
      color: var(--secondary-text-color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .numeric-arrow-input-value {
 | 
			
		||||
      color: var(--primary-text-color);
 | 
			
		||||
      font-size: 16px;
 | 
			
		||||
      font-weight: 500;
 | 
			
		||||
    }
 | 
			
		||||
  `;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
  interface HTMLElementTagNameMap {
 | 
			
		||||
    "ha-numeric-arrow-input": HaNumericArrowInput;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										281
									
								
								src/components/ha-time-picker.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										281
									
								
								src/components/ha-time-picker.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,281 @@
 | 
			
		||||
import type { PropertyValues } from "lit";
 | 
			
		||||
import { css, html, LitElement, nothing } from "lit";
 | 
			
		||||
import { customElement, property, state } from "lit/decorators";
 | 
			
		||||
import { clampValue } from "../data/number";
 | 
			
		||||
import { useAmPm } from "../common/datetime/use_am_pm";
 | 
			
		||||
import { fireEvent } from "../common/dom/fire_event";
 | 
			
		||||
import type { FrontendLocaleData } from "../data/translation";
 | 
			
		||||
import type { HomeAssistant } from "../types";
 | 
			
		||||
import type { ClampedValue } from "../data/number";
 | 
			
		||||
import "./ha-base-time-input";
 | 
			
		||||
import "./ha-button";
 | 
			
		||||
import "./ha-numeric-arrow-input";
 | 
			
		||||
 | 
			
		||||
@customElement("ha-time-picker")
 | 
			
		||||
export class HaTimePicker extends LitElement {
 | 
			
		||||
  @property({ attribute: false }) public hass!: HomeAssistant;
 | 
			
		||||
 | 
			
		||||
  @property({ attribute: false }) public locale!: FrontendLocaleData;
 | 
			
		||||
 | 
			
		||||
  @property({ attribute: false }) public value?: string;
 | 
			
		||||
 | 
			
		||||
  @property({ attribute: false }) public disabled = false;
 | 
			
		||||
 | 
			
		||||
  @property({ attribute: false }) public required = false;
 | 
			
		||||
 | 
			
		||||
  @property({ attribute: false }) public enableSeconds = false;
 | 
			
		||||
 | 
			
		||||
  @state() private _hours = 0;
 | 
			
		||||
 | 
			
		||||
  @state() private _minutes = 0;
 | 
			
		||||
 | 
			
		||||
  @state() private _seconds = 0;
 | 
			
		||||
 | 
			
		||||
  @state() private _useAmPm = false;
 | 
			
		||||
 | 
			
		||||
  @state() private _isPm = false;
 | 
			
		||||
 | 
			
		||||
  protected firstUpdated(changedProperties: PropertyValues) {
 | 
			
		||||
    super.firstUpdated(changedProperties);
 | 
			
		||||
    this._useAmPm = useAmPm(this.locale);
 | 
			
		||||
 | 
			
		||||
    let hours = NaN;
 | 
			
		||||
    let minutes = NaN;
 | 
			
		||||
    let seconds = NaN;
 | 
			
		||||
    let isPm = false;
 | 
			
		||||
 | 
			
		||||
    if (this.value) {
 | 
			
		||||
      const parts = this.value?.split(":") || [];
 | 
			
		||||
      minutes = parts[1] ? Number(parts[1]) : 0;
 | 
			
		||||
      seconds = parts[2] ? Number(parts[2]) : 0;
 | 
			
		||||
      const hour24 = parts[0] ? Number(parts[0]) : 0;
 | 
			
		||||
 | 
			
		||||
      if (this._useAmPm) {
 | 
			
		||||
        if (hour24 === 0) {
 | 
			
		||||
          hours = 12;
 | 
			
		||||
          isPm = false;
 | 
			
		||||
        } else if (hour24 < 12) {
 | 
			
		||||
          hours = hour24;
 | 
			
		||||
          isPm = false;
 | 
			
		||||
        } else if (hour24 === 12) {
 | 
			
		||||
          hours = 12;
 | 
			
		||||
          isPm = true;
 | 
			
		||||
        } else {
 | 
			
		||||
          hours = hour24 - 12;
 | 
			
		||||
          isPm = true;
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        hours = hour24;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this._hours = hours;
 | 
			
		||||
    this._minutes = minutes;
 | 
			
		||||
    this._seconds = seconds;
 | 
			
		||||
    this._isPm = isPm;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected render() {
 | 
			
		||||
    return html`<div class="time-picker-container">
 | 
			
		||||
      <ha-numeric-arrow-input
 | 
			
		||||
        .disabled=${this.disabled}
 | 
			
		||||
        .required=${this.required}
 | 
			
		||||
        .min=${this._useAmPm ? 1 : 0}
 | 
			
		||||
        .max=${this._useAmPm ? 12 : 23}
 | 
			
		||||
        .step=${1}
 | 
			
		||||
        .padStart=${2}
 | 
			
		||||
        .value=${this._hours}
 | 
			
		||||
        @value-changed=${this._hoursChanged}
 | 
			
		||||
        .labelUp=${
 | 
			
		||||
          // TODO: Localize
 | 
			
		||||
          "Increase hours"
 | 
			
		||||
        }
 | 
			
		||||
        .labelDown=${
 | 
			
		||||
          // TODO: Localize
 | 
			
		||||
          "Decrease hours"
 | 
			
		||||
        }
 | 
			
		||||
      ></ha-numeric-arrow-input>
 | 
			
		||||
      <span class="time-picker-separator">:</span>
 | 
			
		||||
      <ha-numeric-arrow-input
 | 
			
		||||
        .disabled=${this.disabled}
 | 
			
		||||
        .required=${this.required}
 | 
			
		||||
        .min=${0}
 | 
			
		||||
        .max=${59}
 | 
			
		||||
        .step=${1}
 | 
			
		||||
        .padStart=${2}
 | 
			
		||||
        .labelUp=${
 | 
			
		||||
          // TODO: Localize
 | 
			
		||||
          "Increase minutes"
 | 
			
		||||
        }
 | 
			
		||||
        .labelDown=${
 | 
			
		||||
          // TODO: Localize
 | 
			
		||||
          "Decrease minutes"
 | 
			
		||||
        }
 | 
			
		||||
        .value=${this._minutes}
 | 
			
		||||
        @value-changed=${this._minutesChanged}
 | 
			
		||||
      ></ha-numeric-arrow-input>
 | 
			
		||||
      ${this.enableSeconds
 | 
			
		||||
        ? html`
 | 
			
		||||
            <span class="time-picker-separator">:</span>
 | 
			
		||||
            <ha-numeric-arrow-input
 | 
			
		||||
              .disabled=${this.disabled}
 | 
			
		||||
              .required=${this.required}
 | 
			
		||||
              .min=${0}
 | 
			
		||||
              .max=${59}
 | 
			
		||||
              .step=${1}
 | 
			
		||||
              .padStart=${2}
 | 
			
		||||
              .labelUp=${
 | 
			
		||||
                // TODO: Localize
 | 
			
		||||
                "Increase seconds"
 | 
			
		||||
              }
 | 
			
		||||
              .labelDown=${
 | 
			
		||||
                // TODO: Localize
 | 
			
		||||
                "Decrease seconds"
 | 
			
		||||
              }
 | 
			
		||||
              .value=${this._seconds}
 | 
			
		||||
              @value-changed=${this._secondsChanged}
 | 
			
		||||
            ></ha-numeric-arrow-input>
 | 
			
		||||
          `
 | 
			
		||||
        : nothing}
 | 
			
		||||
      ${this._useAmPm
 | 
			
		||||
        ? html`
 | 
			
		||||
            <ha-button @click=${this._toggleAmPm}>
 | 
			
		||||
              ${this._isPm ? "PM" : "AM"}
 | 
			
		||||
            </ha-button>
 | 
			
		||||
          `
 | 
			
		||||
        : nothing}
 | 
			
		||||
    </div>`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected updated(changedProperties: PropertyValues) {
 | 
			
		||||
    super.updated(changedProperties);
 | 
			
		||||
 | 
			
		||||
    if (changedProperties.has("_hours")) {
 | 
			
		||||
      this._timeUpdated();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (changedProperties.has("_minutes")) {
 | 
			
		||||
      this._timeUpdated();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (changedProperties.has("_seconds")) {
 | 
			
		||||
      this._timeUpdated();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (changedProperties.has("_useAmPm")) {
 | 
			
		||||
      this._timeUpdated();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (changedProperties.has("_isPm")) {
 | 
			
		||||
      this._timeUpdated();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _hoursChanged(ev: CustomEvent<ClampedValue>) {
 | 
			
		||||
    ev.stopPropagation?.();
 | 
			
		||||
    this._hours = ev.detail.value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _minutesChanged(ev: CustomEvent<ClampedValue>) {
 | 
			
		||||
    ev.stopPropagation?.();
 | 
			
		||||
    this._minutes = ev.detail.value;
 | 
			
		||||
    if (ev.detail.clamped) {
 | 
			
		||||
      if (ev.detail.value === 0) {
 | 
			
		||||
        this._hoursChanged({
 | 
			
		||||
          detail: clampValue({
 | 
			
		||||
            value: this._hours - 1,
 | 
			
		||||
            min: this._useAmPm ? 1 : 0,
 | 
			
		||||
            max: this._useAmPm ? 12 : 23,
 | 
			
		||||
          }),
 | 
			
		||||
        } as CustomEvent<ClampedValue>);
 | 
			
		||||
        this._minutes = 59;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (ev.detail.value === 59) {
 | 
			
		||||
        this._hoursChanged({
 | 
			
		||||
          detail: clampValue({
 | 
			
		||||
            value: this._hours + 1,
 | 
			
		||||
            min: this._useAmPm ? 1 : 0,
 | 
			
		||||
            max: this._useAmPm ? 12 : 23,
 | 
			
		||||
          }),
 | 
			
		||||
        } as CustomEvent<ClampedValue>);
 | 
			
		||||
        const hourMax = this._useAmPm ? 12 : 23;
 | 
			
		||||
        if (this._hours < hourMax) {
 | 
			
		||||
          this._minutes = 0;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _secondsChanged(ev: CustomEvent<ClampedValue>) {
 | 
			
		||||
    ev.stopPropagation?.();
 | 
			
		||||
    this._seconds = ev.detail.value;
 | 
			
		||||
    if (ev.detail.clamped) {
 | 
			
		||||
      if (ev.detail.value === 0) {
 | 
			
		||||
        this._minutesChanged({
 | 
			
		||||
          detail: clampValue({ value: this._minutes - 1, min: 0, max: 59 }),
 | 
			
		||||
        } as CustomEvent<ClampedValue>);
 | 
			
		||||
        this._seconds = 59;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (ev.detail.value === 59) {
 | 
			
		||||
        this._minutesChanged({
 | 
			
		||||
          detail: clampValue({ value: this._minutes + 1, min: 0, max: 59 }),
 | 
			
		||||
        } as CustomEvent<ClampedValue>);
 | 
			
		||||
        const hourMax = this._useAmPm ? 12 : 23;
 | 
			
		||||
        if (!(this._hours === hourMax && this._minutes === 59)) {
 | 
			
		||||
          this._seconds = 0;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _toggleAmPm() {
 | 
			
		||||
    this._isPm = !this._isPm;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _timeUpdated() {
 | 
			
		||||
    let hour24 = this._hours;
 | 
			
		||||
 | 
			
		||||
    if (this._useAmPm) {
 | 
			
		||||
      if (this._hours === 12) {
 | 
			
		||||
        hour24 = this._isPm ? 12 : 0;
 | 
			
		||||
      } else {
 | 
			
		||||
        hour24 = this._isPm ? this._hours + 12 : this._hours;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const timeParts = [
 | 
			
		||||
      hour24.toString().padStart(2, "0"),
 | 
			
		||||
      this._minutes.toString().padStart(2, "0"),
 | 
			
		||||
      this._seconds.toString().padStart(2, "0"),
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    const time = timeParts.join(":");
 | 
			
		||||
    if (time === this.value) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    this.value = time;
 | 
			
		||||
    fireEvent(this, "change");
 | 
			
		||||
    fireEvent(this, "value-changed", { value: time });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static styles = css`
 | 
			
		||||
    .time-picker-container {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      flex-direction: row;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      gap: 4px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .time-picker-separator {
 | 
			
		||||
      color: var(--primary-text-color);
 | 
			
		||||
    }
 | 
			
		||||
  `;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
  interface HTMLElementTagNameMap {
 | 
			
		||||
    "ha-time-picker": HaTimePicker;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -12,3 +12,35 @@ export const getNumberDeviceClassConvertibleUnits = (
 | 
			
		||||
    type: "number/device_class_convertible_units",
 | 
			
		||||
    device_class: deviceClass,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
export interface ClampedValue {
 | 
			
		||||
  clamped: boolean;
 | 
			
		||||
  value: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Clamp a value between a minimum and maximum value
 | 
			
		||||
 * @param value - The value to clamp
 | 
			
		||||
 * @param min - The minimum value
 | 
			
		||||
 * @param max - The maximum value
 | 
			
		||||
 * @returns The clamped value
 | 
			
		||||
 */
 | 
			
		||||
export const clampValue = ({
 | 
			
		||||
  value,
 | 
			
		||||
  min,
 | 
			
		||||
  max,
 | 
			
		||||
}: {
 | 
			
		||||
  value: number;
 | 
			
		||||
  min?: number;
 | 
			
		||||
  max?: number;
 | 
			
		||||
}): ClampedValue => {
 | 
			
		||||
  if (max !== undefined && value > max) {
 | 
			
		||||
    return { clamped: true, value: max };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (min !== undefined && value < min) {
 | 
			
		||||
    return { clamped: true, value: min };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return { clamped: false, value };
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -79,6 +79,13 @@ export const demoPanels: Panels = {
 | 
			
		||||
    config: null,
 | 
			
		||||
    url_path: "energy",
 | 
			
		||||
  },
 | 
			
		||||
  "time-picker": {
 | 
			
		||||
    component_name: "time-picker",
 | 
			
		||||
    icon: "hass:clock-outline",
 | 
			
		||||
    title: "time_picker",
 | 
			
		||||
    config: null,
 | 
			
		||||
    url_path: "time-picker",
 | 
			
		||||
  },
 | 
			
		||||
  // config: {
 | 
			
		||||
  //   component_name: "config",
 | 
			
		||||
  //   icon: "hass:cog",
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,7 @@ const COMPONENTS = {
 | 
			
		||||
  my: () => import("../panels/my/ha-panel-my"),
 | 
			
		||||
  profile: () => import("../panels/profile/ha-panel-profile"),
 | 
			
		||||
  todo: () => import("../panels/todo/ha-panel-todo"),
 | 
			
		||||
  "time-picker": () => import("../panels/time-picker/ha-panel-time-picker"),
 | 
			
		||||
  "media-browser": () =>
 | 
			
		||||
    import("../panels/media-browser/ha-panel-media-browser"),
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -54,6 +54,10 @@ class DeveloperToolsRouter extends HassRouterPage {
 | 
			
		||||
        tag: "developer-tools-debug",
 | 
			
		||||
        load: () => import("./debug/developer-tools-debug"),
 | 
			
		||||
      },
 | 
			
		||||
      "time-picker": {
 | 
			
		||||
        tag: "developer-tools-time-picker",
 | 
			
		||||
        load: () => import("../time-picker/ha-panel-time-picker"),
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -80,6 +80,13 @@ class PanelDeveloperTools extends LitElement {
 | 
			
		||||
          <sl-tab slot="nav" panel="assist" .active=${page === "assist"}
 | 
			
		||||
            >Assist</sl-tab
 | 
			
		||||
          >
 | 
			
		||||
          <sl-tab
 | 
			
		||||
            slot="nav"
 | 
			
		||||
            panel="time-picker"
 | 
			
		||||
            .active=${page === "time-picker"}
 | 
			
		||||
          >
 | 
			
		||||
            Time Picker
 | 
			
		||||
          </sl-tab>
 | 
			
		||||
        </sl-tab-group>
 | 
			
		||||
      </div>
 | 
			
		||||
      <developer-tools-router
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										180
									
								
								src/panels/time-picker/ha-panel-time-picker.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								src/panels/time-picker/ha-panel-time-picker.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,180 @@
 | 
			
		||||
import { html, LitElement, css } from "lit";
 | 
			
		||||
import { customElement, property, state } from "lit/decorators";
 | 
			
		||||
import type { HomeAssistant } from "../../types";
 | 
			
		||||
import "../../components/ha-time-picker";
 | 
			
		||||
import "../../components/ha-card";
 | 
			
		||||
import "../../components/ha-button";
 | 
			
		||||
import "../../components/ha-alert";
 | 
			
		||||
import "../../components/ha-selector/ha-selector";
 | 
			
		||||
 | 
			
		||||
@customElement("developer-tools-time-picker")
 | 
			
		||||
export class DeveloperToolsTimePicker extends LitElement {
 | 
			
		||||
  @property({ attribute: false })
 | 
			
		||||
  public hass!: HomeAssistant;
 | 
			
		||||
 | 
			
		||||
  @property({ type: Boolean, reflect: true })
 | 
			
		||||
  public narrow = false;
 | 
			
		||||
 | 
			
		||||
  @state()
 | 
			
		||||
  private _timeValue = "14:15:00";
 | 
			
		||||
 | 
			
		||||
  @state()
 | 
			
		||||
  private _timeValue2 = "09:05:05";
 | 
			
		||||
 | 
			
		||||
  static get styles() {
 | 
			
		||||
    return css`
 | 
			
		||||
      :host {
 | 
			
		||||
        display: block;
 | 
			
		||||
        padding: 16px;
 | 
			
		||||
        max-width: 800px;
 | 
			
		||||
        margin: 0 auto;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .header {
 | 
			
		||||
        margin-bottom: 24px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .header h1 {
 | 
			
		||||
        margin: 0 0 8px 0;
 | 
			
		||||
        color: var(--primary-text-color);
 | 
			
		||||
        font-size: 28px;
 | 
			
		||||
        font-weight: 400;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .header p {
 | 
			
		||||
        margin: 0;
 | 
			
		||||
        color: var(--secondary-text-color);
 | 
			
		||||
        font-size: 16px;
 | 
			
		||||
        line-height: 1.5;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .section {
 | 
			
		||||
        margin-bottom: 32px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .section h2 {
 | 
			
		||||
        margin: 0 0 16px 0;
 | 
			
		||||
        color: var(--primary-text-color);
 | 
			
		||||
        font-size: 20px;
 | 
			
		||||
        font-weight: 500;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .example {
 | 
			
		||||
        background: var(--card-background-color);
 | 
			
		||||
        border-radius: 12px;
 | 
			
		||||
        padding: 24px;
 | 
			
		||||
        margin-bottom: 16px;
 | 
			
		||||
        border: 1px solid var(--divider-color);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .example h3 {
 | 
			
		||||
        margin: 0 0 16px 0;
 | 
			
		||||
        color: var(--primary-text-color);
 | 
			
		||||
        font-size: 16px;
 | 
			
		||||
        font-weight: 500;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .time-picker-container {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        gap: 16px;
 | 
			
		||||
        margin-bottom: 16px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .value-display {
 | 
			
		||||
        background: var(--secondary-background-color);
 | 
			
		||||
        padding: 8px 12px;
 | 
			
		||||
        border-radius: 6px;
 | 
			
		||||
        font-family: monospace;
 | 
			
		||||
        font-size: 14px;
 | 
			
		||||
        color: var(--primary-text-color);
 | 
			
		||||
        min-width: 100px;
 | 
			
		||||
        text-align: center;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .controls {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        gap: 12px;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        flex-wrap: wrap;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .form-toggle {
 | 
			
		||||
        margin-top: 16px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      @media (max-width: 600px) {
 | 
			
		||||
        :host {
 | 
			
		||||
          padding: 12px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .time-picker-container {
 | 
			
		||||
          flex-direction: column;
 | 
			
		||||
          align-items: stretch;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .controls {
 | 
			
		||||
          flex-direction: column;
 | 
			
		||||
          align-items: stretch;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected render() {
 | 
			
		||||
    return html`
 | 
			
		||||
      <div class="header">
 | 
			
		||||
        <h1>Time picker demo</h1>
 | 
			
		||||
        <p>
 | 
			
		||||
          This page demonstrates the ha-time-picker component with various
 | 
			
		||||
          configurations and use cases.
 | 
			
		||||
        </p>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="section">
 | 
			
		||||
        <h2>Time picker</h2>
 | 
			
		||||
        <div class="example">
 | 
			
		||||
          <div class="time-picker-container">
 | 
			
		||||
            <ha-time-picker
 | 
			
		||||
              .locale=${this.hass.locale}
 | 
			
		||||
              .value=${this._timeValue}
 | 
			
		||||
              @value-changed=${this._onTimeChanged}
 | 
			
		||||
            ></ha-time-picker>
 | 
			
		||||
            <div class="value-display">${this._timeValue}</div>
 | 
			
		||||
          </div>
 | 
			
		||||
          <p>Current value: ${this._timeValue}</p>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="section">
 | 
			
		||||
        <h2>Time picker with seconds</h2>
 | 
			
		||||
        <div class="example">
 | 
			
		||||
          <div class="time-picker-container">
 | 
			
		||||
            <ha-time-picker
 | 
			
		||||
              .locale=${this.hass.locale}
 | 
			
		||||
              .value=${this._timeValue2}
 | 
			
		||||
              .enableSeconds=${true}
 | 
			
		||||
              @value-changed=${this._onTime2Changed}
 | 
			
		||||
            ></ha-time-picker>
 | 
			
		||||
            <div class="value-display">${this._timeValue2}</div>
 | 
			
		||||
          </div>
 | 
			
		||||
          <p>Current value: ${this._timeValue2}</p>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _onTimeChanged(ev: CustomEvent) {
 | 
			
		||||
    this._timeValue = ev.detail.value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _onTime2Changed(ev: CustomEvent) {
 | 
			
		||||
    this._timeValue2 = ev.detail.value;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
  interface HTMLElementTagNameMap {
 | 
			
		||||
    "developer-tools-time-picker": DeveloperToolsTimePicker;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user