diff --git a/packages/web-haptics/README.md b/packages/web-haptics/README.md index 73c292a..c1ce6d9 100644 --- a/packages/web-haptics/README.md +++ b/packages/web-haptics/README.md @@ -112,7 +112,7 @@ Show or hide the haptic feedback toggle switch. ### `WebHaptics.isSupported` -Static boolean — `true` if the device supports the Vibration API. +Static boolean — `true` if the device supports haptics (iPhone and Android). Returns `false` on iPad/iPadOS, desktop, and other non-mobile platforms. ## License diff --git a/packages/web-haptics/src/lib/web-haptics/index.ts b/packages/web-haptics/src/lib/web-haptics/index.ts index e54ca9e..5dae80b 100644 --- a/packages/web-haptics/src/lib/web-haptics/index.ts +++ b/packages/web-haptics/src/lib/web-haptics/index.ts @@ -152,13 +152,38 @@ export class WebHaptics { this.showSwitch = options?.showSwitch ?? false; } - static readonly isSupported: boolean = + private static readonly supportsVibrationAPI: boolean = typeof navigator !== "undefined" && typeof navigator.vibrate === "function"; + private static readonly isMobileDevice: boolean = (() => { + if (typeof navigator === "undefined") return false; + if (navigator.maxTouchPoints <= 0) return false; + + if ("userAgentData" in navigator) { + return (navigator.userAgentData as { mobile: boolean }).mobile === true; + } + + // Safari & Firefox fallback + const userAgent = navigator.userAgent; + if (/Android|iPhone|iPod/.test(userAgent)) return true; + + return false; + })(); + + private static readonly needsTouchFallback = WebHaptics.isMobileDevice && !WebHaptics.supportsVibrationAPI; + + static readonly isSupported: boolean = WebHaptics.isMobileDevice; + async trigger( input: HapticInput = [{ duration: 25, intensity: 0.7 }], options?: TriggerOptions, ): Promise { + + if (!WebHaptics.isSupported && !this.debug) { + console.warn(`[web-haptics] Haptics not supported on this device.`,); + return; + } + const normalized = normalizeInput(input); if (!normalized) return; @@ -186,11 +211,11 @@ export class WebHaptics { } } - if (WebHaptics.isSupported) { + if (WebHaptics.supportsVibrationAPI) { navigator.vibrate(toVibratePattern(vibrations, defaultIntensity)); } - if (!WebHaptics.isSupported || this.debug) { + if (WebHaptics.needsTouchFallback || this.debug) { this.ensureDOM(); if (!this.hapticLabel) return; @@ -222,7 +247,7 @@ export class WebHaptics { cancel(): void { this.stopPattern(); - if (WebHaptics.isSupported) { + if (WebHaptics.supportsVibrationAPI) { navigator.vibrate(0); } } diff --git a/site/src/surfaces/docs/index.tsx b/site/src/surfaces/docs/index.tsx index 59f0731..26b92aa 100644 --- a/site/src/surfaces/docs/index.tsx +++ b/site/src/surfaces/docs/index.tsx @@ -61,7 +61,7 @@ const methods = [ { signature: "WebHaptics.isSupported: boolean", description: - "Static property. Returns true if the device supports the Vibration API.", + "Static property. Returns true if the device supports haptics (iPhone and Android). Returns false on iPad, desktops, and other non-mobile platforms.", }, ];