mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
Add slider bar (#14094)
* Add slider bar * a11y feedbacks * allow undefined values * rename variable * Add end style * Add vertical slider * Allow theming * Improve theming * Improve colors * fix properties * stepped value for aria value * use vertical instead of orientation * Improve a11y * force role slider
This commit is contained in:
parent
928f20ada5
commit
55467666f7
3
gallery/src/pages/components/ha-bar-slider.markdown
Normal file
3
gallery/src/pages/components/ha-bar-slider.markdown
Normal file
@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Bar Sliders
|
||||
---
|
169
gallery/src/pages/components/ha-bar-slider.ts
Normal file
169
gallery/src/pages/components/ha-bar-slider.ts
Normal file
@ -0,0 +1,169 @@
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import "../../../../src/components/ha-bar-slider";
|
||||
import "../../../../src/components/ha-card";
|
||||
|
||||
const sliders: {
|
||||
id: string;
|
||||
label: string;
|
||||
mode?: "start" | "end" | "indicator";
|
||||
class?: string;
|
||||
}[] = [
|
||||
{
|
||||
id: "slider-start",
|
||||
label: "Slider (start mode)",
|
||||
mode: "start",
|
||||
},
|
||||
{
|
||||
id: "slider-end",
|
||||
label: "Slider (end mode)",
|
||||
mode: "end",
|
||||
},
|
||||
{
|
||||
id: "slider-indicator",
|
||||
label: "Slider (indicator mode)",
|
||||
mode: "indicator",
|
||||
},
|
||||
{
|
||||
id: "slider-start-custom",
|
||||
label: "Slider (start mode) and custom style",
|
||||
mode: "start",
|
||||
class: "custom",
|
||||
},
|
||||
{
|
||||
id: "slider-end-custom",
|
||||
label: "Slider (end mode) and custom style",
|
||||
mode: "end",
|
||||
class: "custom",
|
||||
},
|
||||
{
|
||||
id: "slider-indicator-custom",
|
||||
label: "Slider (indicator mode) and custom style",
|
||||
mode: "indicator",
|
||||
class: "custom",
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-components-ha-bar-slider")
|
||||
export class DemoHaBarSlider extends LitElement {
|
||||
@state() private value = 50;
|
||||
|
||||
@state() private sliderPosition?: number;
|
||||
|
||||
handleValueChanged(e: CustomEvent) {
|
||||
this.value = e.detail.value as number;
|
||||
}
|
||||
|
||||
handleSliderMoved(e: CustomEvent) {
|
||||
this.sliderPosition = e.detail.value as number;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<p><b>Slider values</b></p>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>position</td>
|
||||
<td>${this.sliderPosition ?? "-"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>value</td>
|
||||
<td>${this.value ?? "-"}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</ha-card>
|
||||
${repeat(sliders, (slider) => {
|
||||
const { id, label, ...config } = slider;
|
||||
return html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<label id=${id}>${label}</label>
|
||||
<pre>Config: ${JSON.stringify(config)}</pre>
|
||||
<ha-bar-slider
|
||||
.value=${this.value}
|
||||
.mode=${config.mode}
|
||||
class=${ifDefined(config.class)}
|
||||
@value-changed=${this.handleValueChanged}
|
||||
@slider-moved=${this.handleSliderMoved}
|
||||
aria-labelledby=${id}
|
||||
>
|
||||
</ha-bar-slider>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
})}
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<p class="title"><b>Vertical</b></p>
|
||||
<div class="vertical-sliders">
|
||||
${repeat(sliders, (slider) => {
|
||||
const { id, label, ...config } = slider;
|
||||
return html`
|
||||
<ha-bar-slider
|
||||
.value=${this.value}
|
||||
.mode=${config.mode}
|
||||
vertical
|
||||
class=${ifDefined(config.class)}
|
||||
@value-changed=${this.handleValueChanged}
|
||||
@slider-moved=${this.handleSliderMoved}
|
||||
aria-label=${label}
|
||||
>
|
||||
</ha-bar-slider>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 24px auto;
|
||||
}
|
||||
pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
label {
|
||||
font-weight: 600;
|
||||
}
|
||||
.custom {
|
||||
--slider-bar-color: #ffcf4c;
|
||||
--slider-bar-background: #ffcf4c64;
|
||||
--slider-bar-thickness: 100px;
|
||||
--slider-bar-border-radius: 24px;
|
||||
}
|
||||
.vertical-sliders {
|
||||
height: 300px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
p.title {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.vertical-sliders > *:not(:last-child) {
|
||||
margin-right: 4px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-components-ha-bar-slider": DemoHaBarSlider;
|
||||
}
|
||||
}
|
@ -110,6 +110,7 @@
|
||||
"deep-freeze": "^0.0.1",
|
||||
"fuse.js": "^6.0.0",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"hammerjs": "^2.0.8",
|
||||
"hls.js": "^1.2.3",
|
||||
"home-assistant-js-websocket": "^8.0.0",
|
||||
"idb-keyval": "^5.1.3",
|
||||
@ -169,6 +170,7 @@
|
||||
"@types/chromecast-caf-receiver": "5.0.12",
|
||||
"@types/chromecast-caf-sender": "^1.0.3",
|
||||
"@types/glob": "^7",
|
||||
"@types/hammerjs": "^2.0.41",
|
||||
"@types/js-yaml": "^4",
|
||||
"@types/leaflet": "^1",
|
||||
"@types/leaflet-draw": "^1",
|
||||
|
426
src/components/ha-bar-slider.ts
Normal file
426
src/components/ha-bar-slider.ts
Normal file
@ -0,0 +1,426 @@
|
||||
import "hammerjs";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"slider-moved": { value?: number };
|
||||
}
|
||||
}
|
||||
|
||||
const A11Y_KEY_CODES = new Set([
|
||||
"ArrowRight",
|
||||
"ArrowUp",
|
||||
"ArrowLeft",
|
||||
"ArrowDown",
|
||||
"PageUp",
|
||||
"PageDown",
|
||||
"Home",
|
||||
"End",
|
||||
]);
|
||||
|
||||
const getPercentageFromEvent = (e: HammerInput, vertical: boolean) => {
|
||||
if (vertical) {
|
||||
const y = e.center.y;
|
||||
const offset = e.target.getBoundingClientRect().top;
|
||||
const total = e.target.clientHeight;
|
||||
return Math.max(Math.min(1, 1 - (y - offset) / total), 0);
|
||||
}
|
||||
const x = e.center.x;
|
||||
const offset = e.target.getBoundingClientRect().left;
|
||||
const total = e.target.clientWidth;
|
||||
return Math.max(Math.min(1, (x - offset) / total), 0);
|
||||
};
|
||||
|
||||
@customElement("ha-bar-slider")
|
||||
export class HaBarSlider extends LitElement {
|
||||
@property({ type: Boolean })
|
||||
public disabled = false;
|
||||
|
||||
@property()
|
||||
public mode?: "start" | "end" | "indicator" = "start";
|
||||
|
||||
@property({ type: Boolean })
|
||||
public vertical = false;
|
||||
|
||||
@property({ type: Number })
|
||||
public value?: number;
|
||||
|
||||
@property({ type: Number })
|
||||
public step = 1;
|
||||
|
||||
@property({ type: Number })
|
||||
public min = 0;
|
||||
|
||||
@property({ type: Number })
|
||||
public max = 100;
|
||||
|
||||
@property()
|
||||
public label?: string;
|
||||
|
||||
private _mc?: HammerManager;
|
||||
|
||||
@property({ type: Boolean, reflect: true })
|
||||
public pressed = false;
|
||||
|
||||
valueToPercentage(value: number) {
|
||||
return (value - this.min) / (this.max - this.min);
|
||||
}
|
||||
|
||||
percentageToValue(value: number) {
|
||||
return (this.max - this.min) * value + this.min;
|
||||
}
|
||||
|
||||
steppedValue(value: number) {
|
||||
return Math.round(value / this.step) * this.step;
|
||||
}
|
||||
|
||||
boundedValue(value: number) {
|
||||
return Math.min(Math.max(value, this.min), this.max);
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(changedProperties);
|
||||
this.setupListeners();
|
||||
this.setAttribute("role", "slider");
|
||||
if (!this.hasAttribute("tabindex")) {
|
||||
this.setAttribute("tabindex", "0");
|
||||
}
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
if (changedProps.has("value")) {
|
||||
const valuenow = this.steppedValue(this.value ?? 0);
|
||||
this.setAttribute("aria-valuenow", valuenow.toString());
|
||||
}
|
||||
if (changedProps.has("min")) {
|
||||
this.setAttribute("aria-valuemin", this.min.toString());
|
||||
}
|
||||
if (changedProps.has("max")) {
|
||||
this.setAttribute("aria-valuemax", this.max.toString());
|
||||
}
|
||||
if (changedProps.has("vertical")) {
|
||||
const orientation = this.vertical ? "vertical" : "horizontal";
|
||||
this.setAttribute("aria-orientation", orientation);
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.setupListeners();
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this.destroyListeners();
|
||||
}
|
||||
|
||||
@query("#slider")
|
||||
private slider;
|
||||
|
||||
setupListeners() {
|
||||
if (this.slider && !this._mc) {
|
||||
this._mc = new Hammer.Manager(this.slider, {
|
||||
touchAction: this.vertical ? "pan-x" : "pan-y",
|
||||
});
|
||||
this._mc.add(
|
||||
new Hammer.Pan({
|
||||
threshold: 10,
|
||||
direction: Hammer.DIRECTION_ALL,
|
||||
enable: true,
|
||||
})
|
||||
);
|
||||
|
||||
this._mc.add(new Hammer.Tap({ event: "singletap" }));
|
||||
|
||||
let savedValue;
|
||||
this._mc.on("panstart", () => {
|
||||
if (this.disabled) return;
|
||||
this.pressed = true;
|
||||
savedValue = this.value;
|
||||
});
|
||||
this._mc.on("pancancel", () => {
|
||||
if (this.disabled) return;
|
||||
this.pressed = false;
|
||||
this.value = savedValue;
|
||||
});
|
||||
this._mc.on("panmove", (e) => {
|
||||
if (this.disabled) return;
|
||||
const percentage = getPercentageFromEvent(e, this.vertical);
|
||||
this.value = this.percentageToValue(percentage);
|
||||
const value = this.steppedValue(this.value);
|
||||
fireEvent(this, "slider-moved", { value });
|
||||
});
|
||||
this._mc.on("panend", (e) => {
|
||||
if (this.disabled) return;
|
||||
this.pressed = false;
|
||||
const percentage = getPercentageFromEvent(e, this.vertical);
|
||||
this.value = this.steppedValue(this.percentageToValue(percentage));
|
||||
fireEvent(this, "slider-moved", { value: undefined });
|
||||
fireEvent(this, "value-changed", { value: this.value });
|
||||
});
|
||||
|
||||
this._mc.on("singletap", (e) => {
|
||||
if (this.disabled) return;
|
||||
const percentage = getPercentageFromEvent(e, this.vertical);
|
||||
this.value = this.steppedValue(this.percentageToValue(percentage));
|
||||
fireEvent(this, "value-changed", { value: this.value });
|
||||
});
|
||||
|
||||
this.addEventListener("keydown", this._handleKeyDown);
|
||||
this.addEventListener("keyup", this._handleKeyUp);
|
||||
}
|
||||
}
|
||||
|
||||
destroyListeners() {
|
||||
if (this._mc) {
|
||||
this._mc.destroy();
|
||||
this._mc = undefined;
|
||||
}
|
||||
this.removeEventListener("keydown", this._handleKeyDown);
|
||||
this.removeEventListener("keyup", this._handleKeyDown);
|
||||
}
|
||||
|
||||
private get _tenPercentStep() {
|
||||
return Math.max(this.step, (this.max - this.min) / 10);
|
||||
}
|
||||
|
||||
_handleKeyDown(e: KeyboardEvent) {
|
||||
if (!A11Y_KEY_CODES.has(e.code)) return;
|
||||
e.preventDefault();
|
||||
switch (e.code) {
|
||||
case "ArrowRight":
|
||||
case "ArrowUp":
|
||||
this.value = this.boundedValue((this.value ?? 0) + this.step);
|
||||
break;
|
||||
case "ArrowLeft":
|
||||
case "ArrowDown":
|
||||
this.value = this.boundedValue((this.value ?? 0) - this.step);
|
||||
break;
|
||||
case "PageUp":
|
||||
this.value = this.steppedValue(
|
||||
this.boundedValue((this.value ?? 0) + this._tenPercentStep)
|
||||
);
|
||||
break;
|
||||
case "PageDown":
|
||||
this.value = this.steppedValue(
|
||||
this.boundedValue((this.value ?? 0) - this._tenPercentStep)
|
||||
);
|
||||
break;
|
||||
case "Home":
|
||||
this.value = this.min;
|
||||
break;
|
||||
case "End":
|
||||
this.value = this.max;
|
||||
break;
|
||||
}
|
||||
fireEvent(this, "slider-moved", { value: this.value });
|
||||
}
|
||||
|
||||
_handleKeyUp(e: KeyboardEvent) {
|
||||
if (!A11Y_KEY_CODES.has(e.code)) return;
|
||||
e.preventDefault();
|
||||
fireEvent(this, "value-changed", { value: this.value });
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div
|
||||
id="slider"
|
||||
class="slider"
|
||||
style=${styleMap({
|
||||
"--value": `${this.valueToPercentage(this.value ?? 0)}`,
|
||||
})}
|
||||
>
|
||||
<div class="slider-track-background"></div>
|
||||
${this.mode === "indicator"
|
||||
? html`
|
||||
<div
|
||||
class=${classMap({
|
||||
"slider-track-indicator": true,
|
||||
vertical: this.vertical,
|
||||
})}
|
||||
></div>
|
||||
`
|
||||
: html`
|
||||
<div
|
||||
class=${classMap({
|
||||
"slider-track-bar": true,
|
||||
vertical: this.vertical,
|
||||
[this.mode ?? "start"]: true,
|
||||
})}
|
||||
></div>
|
||||
`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
--slider-bar-color: rgb(var(--rgb-primary-color));
|
||||
--slider-bar-background: rgba(var(--rgb-disabled-color), 0.2);
|
||||
--slider-bar-thickness: 40px;
|
||||
--slider-bar-border-radius: 12px;
|
||||
height: var(--slider-bar-thickness);
|
||||
width: 100%;
|
||||
}
|
||||
:host([vertical]) {
|
||||
width: var(--slider-bar-thickness);
|
||||
height: 100%;
|
||||
}
|
||||
.slider {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-radius: var(--slider-bar-border-radius);
|
||||
transform: translateZ(0);
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
.slider * {
|
||||
pointer-events: none;
|
||||
}
|
||||
.slider .slider-track-background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: var(--slider-bar-background);
|
||||
}
|
||||
.slider .slider-track-bar {
|
||||
--border-radius: calc(var(--slider-bar-border-radius) / 2);
|
||||
--handle-size: 4px;
|
||||
--handle-margin: calc(var(--slider-bar-thickness) / 8);
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: var(--slider-bar-color);
|
||||
transition: transform 180ms ease-in-out;
|
||||
}
|
||||
.slider .slider-track-bar::after {
|
||||
display: block;
|
||||
content: "";
|
||||
position: absolute;
|
||||
margin: auto;
|
||||
border-radius: var(--handle-size);
|
||||
background-color: white;
|
||||
}
|
||||
.slider .slider-track-bar {
|
||||
top: 0;
|
||||
left: 0;
|
||||
transform: translate3d(calc((var(--value, 0) - 1) * 100%), 0, 0);
|
||||
border-radius: 0 var(--border-radius) var(--border-radius) 0;
|
||||
}
|
||||
.slider .slider-track-bar:after {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: var(--handle-margin);
|
||||
height: 50%;
|
||||
width: var(--handle-size);
|
||||
}
|
||||
.slider .slider-track-bar.end {
|
||||
right: 0;
|
||||
left: initial;
|
||||
transform: translate3d(calc(var(--value, 0) * 100%), 0, 0);
|
||||
border-radius: var(--border-radius) 0 0 var(--border-radius);
|
||||
}
|
||||
.slider .slider-track-bar.end::after {
|
||||
right: initial;
|
||||
left: var(--handle-margin);
|
||||
}
|
||||
|
||||
.slider .slider-track-bar.vertical {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
transform: translate3d(0, calc((1 - var(--value, 0)) * 100%), 0);
|
||||
border-radius: var(--border-radius) var(--border-radius) 0 0;
|
||||
}
|
||||
.slider .slider-track-bar.vertical:after {
|
||||
top: var(--handle-margin);
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: initial;
|
||||
width: 50%;
|
||||
height: var(--handle-size);
|
||||
}
|
||||
.slider .slider-track-bar.vertical.end {
|
||||
top: 0;
|
||||
bottom: initial;
|
||||
transform: translate3d(0, calc((0 - var(--value, 0)) * 100%), 0);
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
}
|
||||
.slider .slider-track-bar.vertical.end::after {
|
||||
top: initial;
|
||||
bottom: var(--handle-margin);
|
||||
}
|
||||
|
||||
.slider .slider-track-indicator:after {
|
||||
display: block;
|
||||
content: "";
|
||||
background-color: rgb(var(--rgb-secondary-text-color));
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
border-radius: var(--handle-size);
|
||||
}
|
||||
|
||||
.slider .slider-track-indicator {
|
||||
--indicator-size: calc(var(--slider-bar-thickness) / 4);
|
||||
--handle-size: 4px;
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
border-radius: var(--handle-size);
|
||||
transition: left 180ms ease-in-out, bottom 180ms ease-in-out;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: calc(var(--value, 0) * (100% - var(--indicator-size)));
|
||||
width: var(--indicator-size);
|
||||
}
|
||||
.slider .slider-track-indicator:after {
|
||||
height: 50%;
|
||||
width: var(--handle-size);
|
||||
}
|
||||
|
||||
.slider .slider-track-indicator.vertical {
|
||||
top: initial;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: calc(var(--value, 0) * (100% - var(--indicator-size)));
|
||||
height: var(--indicator-size);
|
||||
width: 100%;
|
||||
}
|
||||
.slider .slider-track-indicator.vertical:after {
|
||||
height: var(--handle-size);
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
:host([pressed]) .slider-track-bar,
|
||||
:host([pressed]) .slider-track-indicator {
|
||||
transition: none;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-bar-slider": HaBarSlider;
|
||||
}
|
||||
}
|
16
yarn.lock
16
yarn.lock
@ -3816,6 +3816,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/hammerjs@npm:^2.0.41":
|
||||
version: 2.0.41
|
||||
resolution: "@types/hammerjs@npm:2.0.41"
|
||||
checksum: d16fbd688fc9b18cc270abe8dea8d4c50ef7bd8375e593d92c233d299387933a6b003c8db69819344833052458bc5f9ef1b472001277a49f095928d184356006
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/har-format@npm:*":
|
||||
version: 1.2.4
|
||||
resolution: "@types/har-format@npm:1.2.4"
|
||||
@ -8823,6 +8830,13 @@ fsevents@^1.2.7:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"hammerjs@npm:^2.0.8":
|
||||
version: 2.0.8
|
||||
resolution: "hammerjs@npm:2.0.8"
|
||||
checksum: b092da7d1565a165d7edb53ef0ce212837a8b11f897aa3cf81a7818b66686b0ab3f4747fbce8fc8a41d1376594639ce3a054b0fd4889ca8b5b136a29ca500e27
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"handle-thing@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "handle-thing@npm:2.0.0"
|
||||
@ -9032,6 +9046,7 @@ fsevents@^1.2.7:
|
||||
"@types/chromecast-caf-receiver": 5.0.12
|
||||
"@types/chromecast-caf-sender": ^1.0.3
|
||||
"@types/glob": ^7
|
||||
"@types/hammerjs": ^2.0.41
|
||||
"@types/js-yaml": ^4
|
||||
"@types/leaflet": ^1
|
||||
"@types/leaflet-draw": ^1
|
||||
@ -9085,6 +9100,7 @@ fsevents@^1.2.7:
|
||||
gulp-merge-json: ^1.3.1
|
||||
gulp-rename: ^2.0.0
|
||||
gulp-zopfli-green: ^3.0.1
|
||||
hammerjs: ^2.0.8
|
||||
hls.js: ^1.2.3
|
||||
home-assistant-js-websocket: ^8.0.0
|
||||
html-minifier: ^4.0.0
|
||||
|
Loading…
x
Reference in New Issue
Block a user