Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bright-moles-explode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"web-haptics": patch
---

Fix `WebHaptics.isSupported` so it treats touch fallback devices like iPhone as supported, and add `WebHaptics.supportsVibrationApi` for callers that need the narrower `navigator.vibrate` signal.
8 changes: 7 additions & 1 deletion packages/web-haptics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,13 @@ Show or hide the haptic feedback toggle switch.

### `WebHaptics.isSupported`

Static boolean — `true` if the device supports the Vibration API.
Static boolean — `true` if WebHaptics can attempt haptics on this device,
either through the Vibration API or the touch fallback used on platforms like
iPhone.

### `WebHaptics.supportsVibrationApi`

Static boolean — `true` if the device exposes `navigator.vibrate`.

## License

Expand Down
42 changes: 40 additions & 2 deletions packages/web-haptics/src/lib/web-haptics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,39 @@ function toVibratePattern(

let instanceCounter = 0;

function supportsVibrationApi(): boolean {
return (
typeof navigator !== "undefined" && typeof navigator.vibrate === "function"
);
}

function supportsTouchFallback(): boolean {
if (typeof navigator === "undefined") {
return false;
}

if (navigator.maxTouchPoints > 0) {
return true;
}

if (typeof window === "undefined") {
return false;
}

if ("ontouchstart" in window) {
return true;
}

if (typeof window.matchMedia !== "function") {
return false;
}

return (
window.matchMedia("(pointer: coarse)").matches ||
window.matchMedia("(any-pointer: coarse)").matches
);
}

export class WebHaptics {
private hapticLabel: HTMLLabelElement | null = null;
private domInitialized = false;
Expand All @@ -152,8 +185,13 @@ export class WebHaptics {
this.showSwitch = options?.showSwitch ?? false;
}

static readonly isSupported: boolean =
typeof navigator !== "undefined" && typeof navigator.vibrate === "function";
static get supportsVibrationApi(): boolean {
return supportsVibrationApi();
}

static get isSupported(): boolean {
return supportsVibrationApi() || supportsTouchFallback();
}

async trigger(
input: HapticInput = [{ duration: 25, intensity: 0.7 }],
Expand Down
3 changes: 2 additions & 1 deletion packages/web-haptics/src/react/useWebHaptics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export function useWebHaptics(options?: WebHapticsOptions) {
const cancel = useCallback(() => instanceRef.current?.cancel(), []);

const isSupported = WebHaptics.isSupported;
const supportsVibrationApi = WebHaptics.supportsVibrationApi;

return { trigger, cancel, isSupported };
return { trigger, cancel, isSupported, supportsVibrationApi };
}
10 changes: 9 additions & 1 deletion packages/web-haptics/src/svelte/createWebHaptics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ export function createWebHaptics(options?: WebHapticsOptions) {
const destroy = () => instance.destroy();
const setDebug = (debug: boolean) => instance.setDebug(debug);
const isSupported = WebHaptics.isSupported;
const supportsVibrationApi = WebHaptics.supportsVibrationApi;

return { trigger, cancel, destroy, setDebug, isSupported };
return {
trigger,
cancel,
destroy,
setDebug,
isSupported,
supportsVibrationApi,
};
}
3 changes: 2 additions & 1 deletion packages/web-haptics/src/vue/useWebHaptics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export function useWebHaptics(options?: WebHapticsOptions) {
instance?.trigger(input, options);
const cancel = () => instance?.cancel();
const isSupported = WebHaptics.isSupported;
const supportsVibrationApi = WebHaptics.supportsVibrationApi;

return { trigger, cancel, isSupported };
return { trigger, cancel, isSupported, supportsVibrationApi };
}
13 changes: 9 additions & 4 deletions site/src/surfaces/docs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,12 @@ const methods = [
{
signature: "WebHaptics.isSupported: boolean",
description:
"Static property. Returns true if the device supports the Vibration API.",
"Static property. Returns true if WebHaptics can attempt haptics through either the Vibration API or the touch fallback.",
},
{
signature: "WebHaptics.supportsVibrationApi: boolean",
description:
"Static property. Returns true when the device exposes navigator.vibrate.",
},
];

Expand Down Expand Up @@ -175,7 +180,7 @@ export const Docs = () => {
<CodeBlock
code={`import { useWebHaptics } from 'web-haptics/react';

const { trigger, cancel, isSupported } = useWebHaptics({
const { trigger, cancel, isSupported, supportsVibrationApi } = useWebHaptics({
debug: false,
});`}
/>
Expand All @@ -186,7 +191,7 @@ const { trigger, cancel, isSupported } = useWebHaptics({
<CodeBlock
code={`import { useWebHaptics } from 'web-haptics/vue';

const { trigger, cancel, isSupported } = useWebHaptics();`}
const { trigger, cancel, isSupported, supportsVibrationApi } = useWebHaptics();`}
/>
</details>

Expand All @@ -195,7 +200,7 @@ const { trigger, cancel, isSupported } = useWebHaptics();`}
<CodeBlock
code={`import { createWebHaptics } from 'web-haptics/svelte';

const { trigger, cancel, destroy, isSupported } = createWebHaptics();
const { trigger, cancel, destroy, isSupported, supportsVibrationApi } = createWebHaptics();
onDestroy(destroy);`}
/>
</details>
Expand Down