mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
Avoid thermostat card interaction on scroll on touch devices (#19423)
* Avoid thermostat card interaction on scroll on touch devices * Fix background interaction on safari * Fix interaction in Safari * Make listeners private
This commit is contained in:
parent
9bad3c8101
commit
04f6a01c3d
@ -16,12 +16,19 @@ import {
|
|||||||
nothing,
|
nothing,
|
||||||
svg,
|
svg,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import {
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
query,
|
||||||
|
queryAll,
|
||||||
|
state,
|
||||||
|
} from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { clamp } from "../common/number/clamp";
|
import { clamp } from "../common/number/clamp";
|
||||||
import { svgArc } from "../resources/svg-arc";
|
import { svgArc } from "../resources/svg-arc";
|
||||||
|
import { isTouch } from "../util/is_touch";
|
||||||
|
|
||||||
const MAX_ANGLE = 270;
|
const MAX_ANGLE = 270;
|
||||||
const ROTATE_ANGLE = 360 - MAX_ANGLE / 2 - 90;
|
const ROTATE_ANGLE = 360 - MAX_ANGLE / 2 - 90;
|
||||||
@ -153,11 +160,6 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
return Math.min(Math.max(value, min), max);
|
return Math.min(Math.max(value, min), max);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated(changedProps: PropertyValues): void {
|
|
||||||
super.firstUpdated(changedProps);
|
|
||||||
this._setupListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues): void {
|
protected updated(changedProps: PropertyValues): void {
|
||||||
super.updated(changedProps);
|
super.updated(changedProps);
|
||||||
if (!this._activeSlider) {
|
if (!this._activeSlider) {
|
||||||
@ -171,6 +173,19 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
this._localHigh = this.high;
|
this._localHigh = this.high;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(changedProps.has("_localValue") &&
|
||||||
|
changedProps.get("_localValue") == null) ||
|
||||||
|
(changedProps.has("_localLow") &&
|
||||||
|
changedProps.get("_localLow") == null) ||
|
||||||
|
(changedProps.has("_localHigh") &&
|
||||||
|
changedProps.get("_localHigh") == null) ||
|
||||||
|
changedProps.has("preventInteractionOnScroll")
|
||||||
|
) {
|
||||||
|
this._destroyListeners();
|
||||||
|
this._setupListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback(): void {
|
connectedCallback(): void {
|
||||||
@ -182,7 +197,7 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _mc?: HammerManager;
|
private _managers: HammerManager[] = [];
|
||||||
|
|
||||||
private _getPercentageFromEvent = (e: HammerInput) => {
|
private _getPercentageFromEvent = (e: HammerInput) => {
|
||||||
const bound = this._slider.getBoundingClientRect();
|
const bound = this._slider.getBoundingClientRect();
|
||||||
@ -201,8 +216,8 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
@query("#slider")
|
@query("#slider")
|
||||||
private _slider;
|
private _slider;
|
||||||
|
|
||||||
@query("#interaction")
|
@queryAll("[data-interaction]")
|
||||||
private _interaction;
|
private _interactions?: HTMLElement[];
|
||||||
|
|
||||||
private _findActiveSlider(value: number): ActiveSlider {
|
private _findActiveSlider(value: number): ActiveSlider {
|
||||||
if (!this.dual) return "value";
|
if (!this.dual) return "value";
|
||||||
@ -245,135 +260,148 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
_setupListeners() {
|
private _setupListeners() {
|
||||||
if (this._interaction && !this._mc) {
|
if (this._interactions && this._managers.length === 0) {
|
||||||
this._mc = new Manager(this._interaction, {
|
this._interactions.forEach((interaction) => {
|
||||||
inputClass: TouchMouseInput,
|
const mc = new Manager(interaction, {
|
||||||
});
|
inputClass: TouchMouseInput,
|
||||||
|
});
|
||||||
|
|
||||||
const pressToActivate =
|
this._managers.push(mc);
|
||||||
this.preventInteractionOnScroll && "ontouchstart" in window;
|
|
||||||
|
|
||||||
// If press to activate is true, a 60ms press is required to activate the slider
|
const pressToActivate = this.preventInteractionOnScroll && isTouch;
|
||||||
this._mc.add(
|
|
||||||
new Press({
|
|
||||||
enable: pressToActivate,
|
|
||||||
pointers: 1,
|
|
||||||
time: 60,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const panRecognizer = new Pan({
|
// If press to activate is true, a 50ms press is required to activate the slider
|
||||||
direction: DIRECTION_ALL,
|
mc.add(
|
||||||
enable: !pressToActivate,
|
new Press({
|
||||||
threshold: 0,
|
enable: pressToActivate,
|
||||||
});
|
pointers: 1,
|
||||||
|
time: 50,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
this._mc.add(panRecognizer);
|
const panRecognizer = new Pan({
|
||||||
|
direction: DIRECTION_ALL,
|
||||||
|
enable: !pressToActivate,
|
||||||
|
threshold: 0,
|
||||||
|
});
|
||||||
|
|
||||||
this._mc.add(new Tap({ event: "singletap" }));
|
mc.add(panRecognizer);
|
||||||
|
|
||||||
this._mc.on("press", (e) => {
|
mc.add(new Tap({ event: "singletap" }));
|
||||||
e.srcEvent.stopPropagation();
|
|
||||||
e.srcEvent.preventDefault();
|
|
||||||
if (this.disabled || this.readonly) return;
|
|
||||||
const percentage = this._getPercentageFromEvent(e);
|
|
||||||
const raw = this._percentageToValue(percentage);
|
|
||||||
this._activeSlider = this._findActiveSlider(raw);
|
|
||||||
const bounded = this._boundedValue(raw);
|
|
||||||
this._setActiveValue(bounded);
|
|
||||||
const stepped = this._steppedValue(bounded);
|
|
||||||
if (this._activeSlider) {
|
|
||||||
fireEvent(this, `${this._activeSlider}-changing`, { value: stepped });
|
|
||||||
}
|
|
||||||
panRecognizer.set({ enable: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
this._mc.on("pressup", (e) => {
|
mc.on("press", (e) => {
|
||||||
e.srcEvent.stopPropagation();
|
e.srcEvent.stopPropagation();
|
||||||
e.srcEvent.preventDefault();
|
e.srcEvent.preventDefault();
|
||||||
const percentage = this._getPercentageFromEvent(e);
|
if (this.disabled || this.readonly) return;
|
||||||
const raw = this._percentageToValue(percentage);
|
const percentage = this._getPercentageFromEvent(e);
|
||||||
const bounded = this._boundedValue(raw);
|
const raw = this._percentageToValue(percentage);
|
||||||
const stepped = this._steppedValue(bounded);
|
this._activeSlider = this._findActiveSlider(raw);
|
||||||
this._setActiveValue(stepped);
|
const bounded = this._boundedValue(raw);
|
||||||
if (this._activeSlider) {
|
this._setActiveValue(bounded);
|
||||||
fireEvent(this, `${this._activeSlider}-changing`, {
|
const stepped = this._steppedValue(bounded);
|
||||||
value: undefined,
|
if (this._activeSlider) {
|
||||||
});
|
fireEvent(this, `${this._activeSlider}-changing`, {
|
||||||
fireEvent(this, `${this._activeSlider}-changed`, { value: stepped });
|
value: stepped,
|
||||||
}
|
});
|
||||||
this._activeSlider = undefined;
|
}
|
||||||
});
|
panRecognizer.set({ enable: true });
|
||||||
|
});
|
||||||
|
|
||||||
this._mc.on("pan", (e) => {
|
mc.on("pressup", (e) => {
|
||||||
e.srcEvent.stopPropagation();
|
e.srcEvent.stopPropagation();
|
||||||
e.srcEvent.preventDefault();
|
e.srcEvent.preventDefault();
|
||||||
});
|
const percentage = this._getPercentageFromEvent(e);
|
||||||
this._mc.on("panstart", (e) => {
|
const raw = this._percentageToValue(percentage);
|
||||||
if (this.disabled || this.readonly) return;
|
const bounded = this._boundedValue(raw);
|
||||||
const percentage = this._getPercentageFromEvent(e);
|
const stepped = this._steppedValue(bounded);
|
||||||
const raw = this._percentageToValue(percentage);
|
this._setActiveValue(stepped);
|
||||||
this._activeSlider = this._findActiveSlider(raw);
|
if (this._activeSlider) {
|
||||||
this._lastSlider = this._activeSlider;
|
fireEvent(this, `${this._activeSlider}-changing`, {
|
||||||
this.shadowRoot?.getElementById("#slider")?.focus();
|
value: undefined,
|
||||||
});
|
});
|
||||||
this._mc.on("pancancel", () => {
|
fireEvent(this, `${this._activeSlider}-changed`, {
|
||||||
if (this.disabled || this.readonly) return;
|
value: stepped,
|
||||||
this._activeSlider = undefined;
|
});
|
||||||
if (pressToActivate) {
|
}
|
||||||
panRecognizer.set({ enable: false });
|
this._activeSlider = undefined;
|
||||||
}
|
});
|
||||||
});
|
|
||||||
this._mc.on("panmove", (e) => {
|
mc.on("pan", (e) => {
|
||||||
if (this.disabled || this.readonly) return;
|
e.srcEvent.stopPropagation();
|
||||||
const percentage = this._getPercentageFromEvent(e);
|
e.srcEvent.preventDefault();
|
||||||
const raw = this._percentageToValue(percentage);
|
});
|
||||||
const bounded = this._boundedValue(raw);
|
mc.on("panstart", (e) => {
|
||||||
this._setActiveValue(bounded);
|
if (this.disabled || this.readonly) return;
|
||||||
const stepped = this._steppedValue(bounded);
|
const percentage = this._getPercentageFromEvent(e);
|
||||||
if (this._activeSlider) {
|
const raw = this._percentageToValue(percentage);
|
||||||
fireEvent(this, `${this._activeSlider}-changing`, { value: stepped });
|
this._activeSlider = this._findActiveSlider(raw);
|
||||||
}
|
this._lastSlider = this._activeSlider;
|
||||||
});
|
this.shadowRoot?.getElementById("#slider")?.focus();
|
||||||
this._mc.on("panend", (e) => {
|
});
|
||||||
if (this.disabled || this.readonly) return;
|
mc.on("pancancel", () => {
|
||||||
const percentage = this._getPercentageFromEvent(e);
|
if (this.disabled || this.readonly) return;
|
||||||
const raw = this._percentageToValue(percentage);
|
this._activeSlider = undefined;
|
||||||
const bounded = this._boundedValue(raw);
|
if (pressToActivate) {
|
||||||
const stepped = this._steppedValue(bounded);
|
panRecognizer.set({ enable: false });
|
||||||
this._setActiveValue(stepped);
|
}
|
||||||
if (this._activeSlider) {
|
});
|
||||||
fireEvent(this, `${this._activeSlider}-changing`, {
|
mc.on("panmove", (e) => {
|
||||||
value: undefined,
|
if (this.disabled || this.readonly) return;
|
||||||
});
|
const percentage = this._getPercentageFromEvent(e);
|
||||||
fireEvent(this, `${this._activeSlider}-changed`, { value: stepped });
|
const raw = this._percentageToValue(percentage);
|
||||||
}
|
const bounded = this._boundedValue(raw);
|
||||||
this._activeSlider = undefined;
|
this._setActiveValue(bounded);
|
||||||
if (pressToActivate) {
|
const stepped = this._steppedValue(bounded);
|
||||||
panRecognizer.set({ enable: false });
|
if (this._activeSlider) {
|
||||||
}
|
fireEvent(this, `${this._activeSlider}-changing`, {
|
||||||
});
|
value: stepped,
|
||||||
this._mc.on("singletap", (e) => {
|
});
|
||||||
if (this.disabled || this.readonly) return;
|
}
|
||||||
const percentage = this._getPercentageFromEvent(e);
|
});
|
||||||
const raw = this._percentageToValue(percentage);
|
mc.on("panend", (e) => {
|
||||||
this._activeSlider = this._findActiveSlider(raw);
|
if (this.disabled || this.readonly) return;
|
||||||
const bounded = this._boundedValue(raw);
|
const percentage = this._getPercentageFromEvent(e);
|
||||||
const stepped = this._steppedValue(bounded);
|
const raw = this._percentageToValue(percentage);
|
||||||
this._setActiveValue(stepped);
|
const bounded = this._boundedValue(raw);
|
||||||
if (this._activeSlider) {
|
const stepped = this._steppedValue(bounded);
|
||||||
fireEvent(this, `${this._activeSlider}-changing`, {
|
this._setActiveValue(stepped);
|
||||||
value: undefined,
|
if (this._activeSlider) {
|
||||||
});
|
fireEvent(this, `${this._activeSlider}-changing`, {
|
||||||
fireEvent(this, `${this._activeSlider}-changed`, { value: stepped });
|
value: undefined,
|
||||||
}
|
});
|
||||||
this._lastSlider = this._activeSlider;
|
fireEvent(this, `${this._activeSlider}-changed`, {
|
||||||
this.shadowRoot?.getElementById("#slider")?.focus();
|
value: stepped,
|
||||||
this._activeSlider = undefined;
|
});
|
||||||
if (pressToActivate) {
|
}
|
||||||
panRecognizer.set({ enable: false });
|
this._activeSlider = undefined;
|
||||||
}
|
if (pressToActivate) {
|
||||||
|
panRecognizer.set({ enable: false });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mc.on("singletap", (e) => {
|
||||||
|
if (this.disabled || this.readonly) return;
|
||||||
|
const percentage = this._getPercentageFromEvent(e);
|
||||||
|
const raw = this._percentageToValue(percentage);
|
||||||
|
this._activeSlider = this._findActiveSlider(raw);
|
||||||
|
const bounded = this._boundedValue(raw);
|
||||||
|
const stepped = this._steppedValue(bounded);
|
||||||
|
this._setActiveValue(stepped);
|
||||||
|
if (this._activeSlider) {
|
||||||
|
fireEvent(this, `${this._activeSlider}-changing`, {
|
||||||
|
value: undefined,
|
||||||
|
});
|
||||||
|
fireEvent(this, `${this._activeSlider}-changed`, {
|
||||||
|
value: stepped,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this._lastSlider = this._activeSlider;
|
||||||
|
this.shadowRoot?.getElementById("#slider")?.focus();
|
||||||
|
this._activeSlider = undefined;
|
||||||
|
if (pressToActivate) {
|
||||||
|
panRecognizer.set({ enable: false });
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -447,10 +475,10 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
this._activeSlider = undefined;
|
this._activeSlider = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
destroyListeners() {
|
private _destroyListeners() {
|
||||||
if (this._mc) {
|
if (this._managers.length > 0) {
|
||||||
this._mc.destroy();
|
this._managers.forEach((manager) => manager.destroy());
|
||||||
this._mc = undefined;
|
this._managers = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -486,6 +514,9 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
r: RADIUS,
|
r: RADIUS,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const angle =
|
||||||
|
value != null ? this._valueToPercentage(value) * MAX_ANGLE : undefined;
|
||||||
|
|
||||||
const limit = mode === "end" ? this.max : this.min;
|
const limit = mode === "end" ? this.max : this.min;
|
||||||
|
|
||||||
const current = this.current ?? limit;
|
const current = this.current ?? limit;
|
||||||
@ -527,6 +558,9 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
? this._strokeCircleDashArc(this.current)
|
? this._strokeCircleDashArc(this.current)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
const onlyDotInteraction =
|
||||||
|
(this.preventInteractionOnScroll && isTouch) || false;
|
||||||
|
|
||||||
return svg`
|
return svg`
|
||||||
<g class=${classMap({ inactive: Boolean(this.inactive) })}>
|
<g class=${classMap({ inactive: Boolean(this.inactive) })}>
|
||||||
<path
|
<path
|
||||||
@ -583,6 +617,18 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
${
|
${
|
||||||
targetCircle
|
targetCircle
|
||||||
? svg`
|
? svg`
|
||||||
|
<!-- Use circle instead of path for interaction (Safari doesn't support well pointer-events with stroke-dasharray) -->
|
||||||
|
<circle
|
||||||
|
transform="rotate(${angle} 0 0)"
|
||||||
|
?data-interaction=${onlyDotInteraction}
|
||||||
|
cx=${RADIUS}
|
||||||
|
cy="0"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d=${path}
|
||||||
|
stroke-dasharray=${targetCircle[0]}
|
||||||
|
stroke-dashoffset=${targetCircle[1]}
|
||||||
|
/>
|
||||||
<path
|
<path
|
||||||
class="target-border ${classMap({ [id]: true })}"
|
class="target-border ${classMap({ [id]: true })}"
|
||||||
d=${path}
|
d=${path}
|
||||||
@ -619,6 +665,9 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
? this._strokeCircleDashArc(current)
|
? this._strokeCircleDashArc(current)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
const onlyDotInteraction =
|
||||||
|
(this.preventInteractionOnScroll && isTouch) || false;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<svg
|
<svg
|
||||||
id="slider"
|
id="slider"
|
||||||
@ -634,13 +683,10 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
id="container"
|
id="container"
|
||||||
transform="translate(160 160) rotate(${ROTATE_ANGLE})"
|
transform="translate(160 160) rotate(${ROTATE_ANGLE})"
|
||||||
>
|
>
|
||||||
<g id="interaction">
|
<path d=${trackPath} ?data-interaction=${!onlyDotInteraction} />
|
||||||
<path d=${trackPath} />
|
<path class="background" d=${trackPath} />
|
||||||
</g>
|
${currentStroke
|
||||||
<g id="display">
|
? svg`
|
||||||
<path class="background" d=${trackPath} />
|
|
||||||
${currentStroke
|
|
||||||
? svg`
|
|
||||||
<path
|
<path
|
||||||
class="current"
|
class="current"
|
||||||
d=${trackPath}
|
d=${trackPath}
|
||||||
@ -648,18 +694,17 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
stroke-dashoffset=${currentStroke[1]}
|
stroke-dashoffset=${currentStroke[1]}
|
||||||
/>
|
/>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
${lowValue != null || this.mode === "full"
|
${lowValue != null || this.mode === "full"
|
||||||
? this.renderArc(
|
? this.renderArc(
|
||||||
this.dual ? "low" : "value",
|
this.dual ? "low" : "value",
|
||||||
lowValue,
|
lowValue,
|
||||||
(!this.dual && this.mode) || "start"
|
(!this.dual && this.mode) || "start"
|
||||||
)
|
)
|
||||||
: nothing}
|
: nothing}
|
||||||
${this.dual && highValue != null
|
${this.dual && highValue != null
|
||||||
? this.renderArc("high", highValue, "end")
|
? this.renderArc("high", highValue, "end")
|
||||||
: nothing}
|
: nothing}
|
||||||
</g>
|
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
`;
|
`;
|
||||||
@ -684,26 +729,34 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
svg {
|
svg {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
g {
|
||||||
|
fill: none;
|
||||||
}
|
}
|
||||||
#slider {
|
#slider {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
#interaction {
|
path[data-interaction] {
|
||||||
display: flex;
|
|
||||||
fill: none;
|
fill: none;
|
||||||
|
cursor: pointer;
|
||||||
|
pointer-events: auto;
|
||||||
stroke: transparent;
|
stroke: transparent;
|
||||||
stroke-linecap: round;
|
stroke-linecap: round;
|
||||||
stroke-width: calc(
|
stroke-width: calc(
|
||||||
24px + 2 * var(--control-circular-slider-interaction-margin)
|
24px + 2 * var(--control-circular-slider-interaction-margin)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
circle[data-interaction] {
|
||||||
|
r: calc(12px + var(--control-circular-slider-interaction-margin));
|
||||||
|
fill: transparent;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
#display {
|
:host([disabled]) [data-interaction],
|
||||||
pointer-events: none;
|
:host([readonly]) [data-interaction] {
|
||||||
}
|
|
||||||
:host([disabled]) #interaction,
|
|
||||||
:host([readonly]) #interaction {
|
|
||||||
cursor: initial;
|
cursor: initial;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.background {
|
.background {
|
||||||
|
@ -14,12 +14,7 @@ import {
|
|||||||
ActionHandlerDetail,
|
ActionHandlerDetail,
|
||||||
ActionHandlerOptions,
|
ActionHandlerOptions,
|
||||||
} from "../../../../data/lovelace/action_handler";
|
} from "../../../../data/lovelace/action_handler";
|
||||||
|
import { isTouch } from "../../../../util/is_touch";
|
||||||
const isTouch =
|
|
||||||
"ontouchstart" in window ||
|
|
||||||
navigator.maxTouchPoints > 0 ||
|
|
||||||
// @ts-ignore
|
|
||||||
navigator.msMaxTouchPoints > 0;
|
|
||||||
|
|
||||||
interface ActionHandlerType extends HTMLElement {
|
interface ActionHandlerType extends HTMLElement {
|
||||||
holdTime: number;
|
holdTime: number;
|
||||||
|
@ -65,6 +65,10 @@ export const stateControlCircularSliderStyle = css`
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.buttons > * {
|
||||||
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
.primary-state {
|
.primary-state {
|
||||||
font-size: 36px;
|
font-size: 36px;
|
||||||
|
5
src/util/is_touch.ts
Normal file
5
src/util/is_touch.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export const isTouch =
|
||||||
|
"ontouchstart" in window ||
|
||||||
|
navigator.maxTouchPoints > 0 ||
|
||||||
|
// @ts-ignore
|
||||||
|
navigator.msMaxTouchPoints > 0;
|
Loading…
x
Reference in New Issue
Block a user