diff --git a/src/fake_data/provide_hass.ts b/src/fake_data/provide_hass.ts
index eb711cc348..bdb58eee00 100644
--- a/src/fake_data/provide_hass.ts
+++ b/src/fake_data/provide_hass.ts
@@ -178,6 +178,7 @@ export const provideHass = (
translationMetadata: translationMetadata as any,
dockedSidebar: "auto",
+ vibrate: true,
moreInfoEntityId: null as any,
async callService(domain, service, data) {
if (data && "entity_id" in data) {
diff --git a/src/panels/profile/ha-force-narrow-row.ts b/src/panels/profile/ha-force-narrow-row.ts
index e490d563c9..86afd56681 100644
--- a/src/panels/profile/ha-force-narrow-row.ts
+++ b/src/panels/profile/ha-force-narrow-row.ts
@@ -15,7 +15,7 @@ import { fireEvent } from "../../common/dom/fire_event";
import { HaSwitch } from "../../components/ha-switch";
@customElement("ha-force-narrow-row")
-class HaPushNotificationsRow extends LitElement {
+class HaForcedNarrowRow extends LitElement {
@property() public hass!: HomeAssistant;
@property() public narrow!: boolean;
@@ -49,6 +49,6 @@ class HaPushNotificationsRow extends LitElement {
declare global {
interface HTMLElementTagNameMap {
- "ha-force-narrow-row": HaPushNotificationsRow;
+ "ha-force-narrow-row": HaForcedNarrowRow;
}
}
diff --git a/src/panels/profile/ha-panel-profile.ts b/src/panels/profile/ha-panel-profile.ts
index 02b485acc5..1848660140 100644
--- a/src/panels/profile/ha-panel-profile.ts
+++ b/src/panels/profile/ha-panel-profile.ts
@@ -24,6 +24,7 @@ import "./ha-pick-language-row";
import "./ha-pick-theme-row";
import "./ha-push-notifications-row";
import "./ha-force-narrow-row";
+import "./ha-set-vibrate-row";
import {
LitElement,
TemplateResult,
@@ -105,6 +106,14 @@ class HaPanelProfile extends LitElement {
>
`
: ""}
+ ${navigator.vibrate
+ ? html`
+
+ `
+ : ""}
+
+ ${this.hass.localize("ui.panel.profile.vibrate.header")}
+
+
+ ${this.hass.localize("ui.panel.profile.vibrate.description")}
+
+
+
+ `;
+ }
+
+ private async _checkedChanged(ev: Event) {
+ const vibrate = (ev.target as HaSwitch).checked;
+ if (vibrate === this.hass.vibrate) {
+ return;
+ }
+ fireEvent(this, "hass-vibrate", {
+ vibrate,
+ });
+ forwardHaptic("light");
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-set-vibrate-row": HaSetVibrateRow;
+ }
+}
diff --git a/src/state/connection-mixin.ts b/src/state/connection-mixin.ts
index d3ecd7b1e0..eb439c8135 100644
--- a/src/state/connection-mixin.ts
+++ b/src/state/connection-mixin.ts
@@ -45,6 +45,7 @@ export const connectionMixin = (
translationMetadata,
dockedSidebar: "docked",
+ vibrate: true,
moreInfoEntityId: null,
hassUrl: (path = "") => new URL(path, auth.data.hassUrl).toString(),
callService: async (domain, service, serviceData = {}) => {
diff --git a/src/state/haptic-mixin.ts b/src/state/haptic-mixin.ts
index 58bd47129a..44c02664c9 100644
--- a/src/state/haptic-mixin.ts
+++ b/src/state/haptic-mixin.ts
@@ -2,6 +2,24 @@ import { Constructor, LitElement, PropertyValues } from "lit-element";
import { HassBaseEl } from "./hass-base-mixin";
import { HapticType } from "../data/haptics";
+import { HomeAssistant } from "../types";
+import { HASSDomEvent } from "../common/dom/fire_event";
+import { storeState } from "../util/ha-pref-storage";
+
+interface VibrateParams {
+ vibrate: HomeAssistant["vibrate"];
+}
+
+declare global {
+ // for fire event
+ interface HASSDomEvents {
+ "hass-vibrate": VibrateParams;
+ }
+ // for add event listener
+ interface HTMLElementEventMap {
+ "hass-vibrate": HASSDomEvent;
+ }
+}
const hapticPatterns = {
success: [50, 50, 50],
@@ -13,16 +31,30 @@ const hapticPatterns = {
selection: [20],
};
-const handleHaptic = (hapticType: HapticType) => {
- navigator.vibrate(hapticPatterns[hapticType]);
+const handleHaptic = (hapticTypeEvent: HASSDomEvent) => {
+ navigator.vibrate(hapticPatterns[hapticTypeEvent.detail]);
};
export const hapticMixin = (superClass: Constructor) =>
class extends superClass {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
- if (navigator.vibrate) {
- window.addEventListener("haptic", (e) => handleHaptic(e.detail));
+ this.addEventListener("hass-vibrate", (ev) => {
+ const vibrate = ev.detail.vibrate;
+ if (navigator.vibrate && vibrate) {
+ window.addEventListener("haptic", handleHaptic);
+ } else {
+ window.removeEventListener("haptic", handleHaptic);
+ }
+ this._updateHass({ vibrate });
+ storeState(this.hass!);
+ });
+ }
+
+ protected hassConnected() {
+ super.hassConnected();
+ if (navigator.vibrate && this.hass!.vibrate) {
+ window.addEventListener("haptic", handleHaptic);
}
}
};
diff --git a/src/translations/en.json b/src/translations/en.json
index 5b801f9d2b..bc60381a98 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -1214,6 +1214,10 @@
"header": "Always hide the sidebar",
"description": "This will hide the sidebar by default, similar to the mobile experience."
},
+ "vibrate": {
+ "header": "Vibrate",
+ "description": "Enable or disable vibration on this device when controlling devices."
+ },
"push_notifications": {
"header": "Push Notifications",
"description": "Send notifications to this device.",
diff --git a/src/types.ts b/src/types.ts
index 81074f14b5..d67d460285 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -139,6 +139,7 @@ export interface HomeAssistant {
localize: LocalizeFunc;
translationMetadata: TranslationMetadata;
+ vibrate: boolean;
dockedSidebar: "docked" | "always_hidden" | "auto";
moreInfoEntityId: string | null;
user?: CurrentUser;
diff --git a/src/util/ha-pref-storage.ts b/src/util/ha-pref-storage.ts
index 6d9c681725..0b2ed29f0f 100644
--- a/src/util/ha-pref-storage.ts
+++ b/src/util/ha-pref-storage.ts
@@ -1,6 +1,11 @@
import { HomeAssistant } from "../types";
-const STORED_STATE = ["dockedSidebar", "selectedTheme", "selectedLanguage"];
+const STORED_STATE = [
+ "dockedSidebar",
+ "selectedTheme",
+ "selectedLanguage",
+ "vibrate",
+];
const STORAGE = window.localStorage || {};
export function storeState(hass: HomeAssistant) {
@@ -27,7 +32,6 @@ export function getState() {
state[key] = value;
}
}
-
return state;
}