web-haptics 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/chunk-42RGEEVN.mjs +3 -0
- package/dist/chunk-42RGEEVN.mjs.map +1 -0
- package/dist/index.d.mts +39 -14
- package/dist/index.d.ts +39 -14
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.d.mts +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +1 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +1 -1
- package/dist/react/index.mjs.map +1 -1
- package/dist/svelte/index.d.mts +8 -3
- package/dist/svelte/index.d.ts +8 -3
- package/dist/svelte/index.js +1 -1
- package/dist/svelte/index.js.map +1 -1
- package/dist/svelte/index.mjs +1 -1
- package/dist/svelte/index.mjs.map +1 -1
- package/dist/types-DkzcH7AA.d.mts +20 -0
- package/dist/types-DkzcH7AA.d.ts +20 -0
- package/dist/vue/index.d.mts +8 -3
- package/dist/vue/index.d.ts +8 -3
- package/dist/vue/index.js +1 -1
- package/dist/vue/index.js.map +1 -1
- package/dist/vue/index.mjs +1 -1
- package/dist/vue/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-YYW6B7NQ.mjs +0 -3
- package/dist/chunk-YYW6B7NQ.mjs.map +0 -1
- package/dist/types-kcUJN2zf.d.mts +0 -15
- package/dist/types-kcUJN2zf.d.ts +0 -15
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# WebHaptics
|
|
2
2
|
|
|
3
|
-
Haptic feedback for the web. Supports React, Vue, and Svelte.
|
|
3
|
+
Haptic feedback for the mobile web. Supports React, Vue, and Svelte.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -64,7 +64,7 @@ haptics.trigger("success");
|
|
|
64
64
|
| `success` | `[50, 50, 50]` | 0.5 |
|
|
65
65
|
| `nudge` | `[80, 100, 200]` | 0.5 |
|
|
66
66
|
| `error` | `[50, 50, 50, 50, 50]` | 0.75 |
|
|
67
|
-
| `
|
|
67
|
+
| `buzz` | `[1000]` | 1.0 |
|
|
68
68
|
|
|
69
69
|
You can also pass custom patterns directly:
|
|
70
70
|
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
var b={success:{pattern:[{duration:50},{delay:50,duration:50}],description:"A series of taps indicating success."},nudge:{pattern:[{duration:80,intensity:.8},{delay:80,duration:50,intensity:.3}],description:"A series of taps indicating a nudge."},error:{pattern:[{duration:50,intensity:.75},{delay:50,duration:50,intensity:.75},{delay:50,duration:50,intensity:.75}],description:"A series of taps indicating a warning or error."},buzz:{pattern:[{duration:1e3,intensity:1}],description:"A long vibration."}};var v=16,g=184,m=1e3,f=20;function x(a){if(typeof a=="number")return{vibrations:[{duration:a}]};if(typeof a=="string"){let i=b[a];return i?{vibrations:i.pattern.map(t=>({...t}))}:(console.warn(`[web-haptics] Unknown preset: "${a}"`),null)}if(Array.isArray(a)){if(a.length===0)return{vibrations:[]};if(typeof a[0]=="number"){let i=a,t=[];for(let e=0;e<i.length;e+=2){let n=e>0?i[e-1]:0;t.push({...n>0&&{delay:n},duration:i[e]})}return{vibrations:t}}return{vibrations:a.map(i=>({...i}))}}return{vibrations:a.pattern.map(i=>({...i}))}}function C(a,i){if(i>=1)return[a];if(i<=0)return[];let t=Math.max(1,Math.round(f*i)),e=f-t,n=[],o=a;for(;o>=f;)n.push(t),n.push(e),o-=f;if(o>0){let s=Math.max(1,Math.round(o*i));n.push(s);let r=o-s;r>0&&n.push(r)}return n}function w(a,i){let t=[];for(let e=0;e<a.length;e++){let n=a[e],o=Math.max(0,Math.min(1,n.intensity??i)),s=n.delay??0;s>0&&(t.length>0&&t.length%2===0?t[t.length-1]+=s:(t.length===0&&t.push(0),t.push(s)));let r=C(n.duration,o);if(r.length===0){t.length>0&&t.length%2===0?t[t.length-1]+=n.duration:n.duration>0&&(t.push(0),t.push(n.duration));continue}for(let d of r)t.push(d)}return t}var I=0,y=class a{hapticLabel=null;domInitialized=!1;instanceId;debug;showSwitch;rafId=null;patternResolve=null;audioCtx=null;audioFilter=null;audioGain=null;audioBuffer=null;constructor(i){this.instanceId=++I,this.debug=i?.debug??!1,this.showSwitch=i?.showSwitch??!1}static isSupported=typeof navigator<"u"&&typeof navigator.vibrate=="function";async trigger(i=[{duration:10}],t){let e=x(i);if(!e)return;let{vibrations:n}=e;if(n.length===0)return;let o=Math.max(0,Math.min(1,t?.intensity??.5));for(let s of n)if(s.duration>m&&(s.duration=m),!Number.isFinite(s.duration)||s.duration<0||s.delay!==void 0&&(!Number.isFinite(s.delay)||s.delay<0)){console.warn("[web-haptics] Invalid vibration values. Durations and delays must be finite non-negative numbers.");return}if(a.isSupported&&navigator.vibrate(w(n,o)),!a.isSupported||this.debug){if(this.ensureDOM(),!this.hapticLabel)return;if(this.debug&&await this.ensureAudio(),this.stopPattern(),this.hapticLabel.click(),this.debug&&this.audioCtx){let s=Math.max(0,Math.min(1,n[0].intensity??o));this.playClick(s)}await this.runPattern(n,o)}}cancel(){this.stopPattern(),a.isSupported&&navigator.vibrate(0)}destroy(){this.stopPattern(),this.hapticLabel&&(this.hapticLabel.remove(),this.hapticLabel=null,this.domInitialized=!1),this.audioCtx&&(this.audioCtx.close(),this.audioCtx=null,this.audioFilter=null,this.audioGain=null,this.audioBuffer=null)}setDebug(i){this.debug=i,!i&&this.audioCtx&&(this.audioCtx.close(),this.audioCtx=null,this.audioFilter=null,this.audioGain=null,this.audioBuffer=null)}setShowSwitch(i){if(this.showSwitch=i,this.hapticLabel){let t=this.hapticLabel.querySelector("input");this.hapticLabel.style.display=i?"":"none",t&&(t.style.display=i?"":"none")}}stopPattern(){this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.patternResolve?.(),this.patternResolve=null}runPattern(i,t){return new Promise(e=>{this.patternResolve=e;let n=[],o=0;for(let u of i){let h=Math.max(0,Math.min(1,u.intensity??t)),l=u.delay??0;l>0&&(o+=l,n.push({end:o,isOn:!1,intensity:0})),o+=u.duration,n.push({end:o,isOn:!0,intensity:h})}let s=o,r=0,d=-1,p=u=>{r===0&&(r=u);let h=u-r;if(h>=s){this.rafId=null,this.patternResolve=null,e();return}let l=n[0];for(let c of n)if(h<c.end){l=c;break}if(l.isOn){let c=v+(1-l.intensity)*g;d===-1?d=u:u-d>=c&&(this.hapticLabel?.click(),this.debug&&this.audioCtx&&this.playClick(l.intensity),d=u)}this.rafId=requestAnimationFrame(p)};this.rafId=requestAnimationFrame(p)})}playClick(i){if(!this.audioCtx||!this.audioFilter||!this.audioGain||!this.audioBuffer)return;let t=this.audioBuffer.getChannelData(0);for(let n=0;n<t.length;n++)t[n]=(Math.random()*2-1)*Math.exp(-n/25);this.audioGain.gain.value=.5*i;let e=this.audioCtx.createBufferSource();e.buffer=this.audioBuffer,e.connect(this.audioFilter),e.onended=()=>e.disconnect(),e.start()}async ensureAudio(){if(!this.audioCtx&&typeof AudioContext<"u"){this.audioCtx=new AudioContext,this.audioFilter=this.audioCtx.createBiquadFilter(),this.audioFilter.type="bandpass",this.audioFilter.frequency.value=4e3,this.audioFilter.Q.value=8,this.audioGain=this.audioCtx.createGain(),this.audioFilter.connect(this.audioGain),this.audioGain.connect(this.audioCtx.destination);let i=.004;this.audioBuffer=this.audioCtx.createBuffer(1,this.audioCtx.sampleRate*i,this.audioCtx.sampleRate);let t=this.audioBuffer.getChannelData(0);for(let e=0;e<t.length;e++)t[e]=(Math.random()*2-1)*Math.exp(-e/25)}this.audioCtx?.state==="suspended"&&await this.audioCtx.resume()}ensureDOM(){if(this.domInitialized||typeof document>"u")return;let i=`web-haptics-${this.instanceId}`,t=document.createElement("label");t.setAttribute("for",i),t.textContent="Haptic feedback",t.style.position="fixed",t.style.bottom="10px",t.style.left="10px",t.style.padding="5px 10px",t.style.backgroundColor="rgba(0, 0, 0, 0.7)",t.style.color="white",t.style.fontFamily="sans-serif",t.style.fontSize="14px",t.style.borderRadius="4px",t.style.zIndex="9999",t.style.userSelect="none",this.hapticLabel=t;let e=document.createElement("input");e.type="checkbox",e.setAttribute("switch",""),e.id=i,e.style.all="initial",e.style.appearance="auto",this.showSwitch||(t.style.display="none",e.style.display="none"),t.appendChild(e),document.body.appendChild(t),this.domInitialized=!0}};export{b as a,y as b};
|
|
3
|
+
//# sourceMappingURL=chunk-42RGEEVN.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/web-haptics/patterns.ts","../src/lib/web-haptics/index.ts"],"sourcesContent":["import type { HapticPreset } from \"./types\";\n\nexport const defaultPatterns = {\n success: {\n pattern: [{ duration: 50 }, { delay: 50, duration: 50 }],\n description: \"A series of taps indicating success.\",\n },\n nudge: {\n pattern: [\n { duration: 80, intensity: 0.8 },\n { delay: 80, duration: 50, intensity: 0.3 },\n ],\n description: \"A series of taps indicating a nudge.\",\n },\n error: {\n pattern: [\n { duration: 50, intensity: 0.75 },\n { delay: 50, duration: 50, intensity: 0.75 },\n { delay: 50, duration: 50, intensity: 0.75 },\n ],\n description: \"A series of taps indicating a warning or error.\",\n },\n buzz: {\n pattern: [{ duration: 1000, intensity: 1 }],\n description: \"A long vibration.\",\n },\n} as const satisfies Record<string, HapticPreset>;\n","import { defaultPatterns } from \"./patterns\";\nimport type {\n HapticInput,\n TriggerOptions,\n Vibration,\n WebHapticsOptions,\n} from \"./types\";\n\nconst TOGGLE_MIN = 16; // ms at intensity 1 (every frame)\nconst TOGGLE_MAX = 184; // range above min (0.5 intensity ≈ 100ms)\nconst MAX_PHASE_MS = 1000; // browser haptic window limit\nconst PWM_CYCLE = 20; // ms per intensity modulation cycle\n\n/** Convert any HapticInput into a Vibration array. */\nfunction normalizeInput(input: HapticInput): {\n vibrations: Vibration[];\n} | null {\n if (typeof input === \"number\") {\n return { vibrations: [{ duration: input }] };\n }\n\n if (typeof input === \"string\") {\n const preset = defaultPatterns[input as keyof typeof defaultPatterns];\n if (!preset) {\n console.warn(`[web-haptics] Unknown preset: \"${input}\"`);\n return null;\n }\n return { vibrations: preset.pattern.map((v) => ({ ...v })) };\n }\n\n if (Array.isArray(input)) {\n if (input.length === 0) return { vibrations: [] };\n\n // number[] shorthand — alternating on/off\n if (typeof input[0] === \"number\") {\n const nums = input as number[];\n const vibrations: Vibration[] = [];\n for (let i = 0; i < nums.length; i += 2) {\n const delay = i > 0 ? nums[i - 1]! : 0;\n vibrations.push({\n ...(delay > 0 && { delay }),\n duration: nums[i]!,\n });\n }\n return { vibrations };\n }\n\n // Vibration[]\n return { vibrations: (input as Vibration[]).map((v) => ({ ...v })) };\n }\n\n // HapticPreset\n return { vibrations: input.pattern.map((v) => ({ ...v })) };\n}\n\n/**\n * Apply PWM modulation to a single vibration duration at a given intensity.\n * Returns the flat on/off segments for this vibration.\n */\nfunction modulateVibration(duration: number, intensity: number): number[] {\n if (intensity >= 1) return [duration];\n if (intensity <= 0) return [];\n\n const onTime = Math.max(1, Math.round(PWM_CYCLE * intensity));\n const offTime = PWM_CYCLE - onTime;\n const result: number[] = [];\n\n let remaining = duration;\n while (remaining >= PWM_CYCLE) {\n result.push(onTime);\n result.push(offTime);\n remaining -= PWM_CYCLE;\n }\n if (remaining > 0) {\n const remOn = Math.max(1, Math.round(remaining * intensity));\n result.push(remOn);\n const remOff = remaining - remOn;\n if (remOff > 0) result.push(remOff);\n }\n\n return result;\n}\n\n/**\n * Convert Vibration[] to the flat number[] pattern for navigator.vibrate(),\n * applying per-vibration PWM intensity modulation.\n */\nfunction toVibratePattern(\n vibrations: Vibration[],\n defaultIntensity: number,\n): number[] {\n const result: number[] = [];\n\n for (let i = 0; i < vibrations.length; i++) {\n const vib = vibrations[i]!;\n const intensity = Math.max(\n 0,\n Math.min(1, vib.intensity ?? defaultIntensity),\n );\n const delay = vib.delay ?? 0;\n\n // Prepend delay: merge into trailing off-time or add new gap\n if (delay > 0) {\n if (result.length > 0 && result.length % 2 === 0) {\n result[result.length - 1]! += delay;\n } else {\n if (result.length === 0) result.push(0);\n result.push(delay);\n }\n }\n\n const modulated = modulateVibration(vib.duration, intensity);\n\n if (modulated.length === 0) {\n // Zero intensity — treat vibration as silence\n if (result.length > 0 && result.length % 2 === 0) {\n result[result.length - 1]! += vib.duration;\n } else if (vib.duration > 0) {\n result.push(0);\n result.push(vib.duration);\n }\n continue;\n }\n\n // Append modulated vibration segments\n for (const seg of modulated) {\n result.push(seg);\n }\n }\n\n return result;\n}\n\nlet instanceCounter = 0;\n\nexport class WebHaptics {\n private hapticLabel: HTMLLabelElement | null = null;\n private domInitialized = false;\n private instanceId: number;\n private debug: boolean;\n private showSwitch: boolean;\n private rafId: number | null = null;\n private patternResolve: (() => void) | null = null;\n private audioCtx: AudioContext | null = null;\n private audioFilter: BiquadFilterNode | null = null;\n private audioGain: GainNode | null = null;\n private audioBuffer: AudioBuffer | null = null;\n\n constructor(options?: WebHapticsOptions) {\n this.instanceId = ++instanceCounter;\n this.debug = options?.debug ?? false;\n this.showSwitch = options?.showSwitch ?? false;\n }\n\n static readonly isSupported: boolean =\n typeof navigator !== \"undefined\" &&\n typeof navigator.vibrate === \"function\";\n\n async trigger(\n input: HapticInput = [{ duration: 10 }],\n options?: TriggerOptions,\n ): Promise<void> {\n const normalized = normalizeInput(input);\n if (!normalized) return;\n\n const { vibrations } = normalized;\n if (vibrations.length === 0) return;\n\n const defaultIntensity = Math.max(\n 0,\n Math.min(1, options?.intensity ?? 0.5),\n );\n\n // Validate and clamp durations\n for (const vib of vibrations) {\n if (vib.duration > MAX_PHASE_MS) vib.duration = MAX_PHASE_MS;\n if (\n !Number.isFinite(vib.duration) ||\n vib.duration < 0 ||\n (vib.delay !== undefined &&\n (!Number.isFinite(vib.delay) || vib.delay < 0))\n ) {\n console.warn(\n `[web-haptics] Invalid vibration values. Durations and delays must be finite non-negative numbers.`,\n );\n return;\n }\n }\n\n if (WebHaptics.isSupported) {\n navigator.vibrate(toVibratePattern(vibrations, defaultIntensity));\n }\n\n if (!WebHaptics.isSupported || this.debug) {\n this.ensureDOM();\n if (!this.hapticLabel) return;\n\n if (this.debug) {\n await this.ensureAudio();\n }\n\n this.stopPattern();\n\n // Fire first click synchronously to stay within user gesture context\n this.hapticLabel.click();\n if (this.debug && this.audioCtx) {\n const firstIntensity = Math.max(\n 0,\n Math.min(1, vibrations[0]!.intensity ?? defaultIntensity),\n );\n this.playClick(firstIntensity);\n }\n\n await this.runPattern(vibrations, defaultIntensity);\n }\n }\n\n cancel(): void {\n this.stopPattern();\n if (WebHaptics.isSupported) {\n navigator.vibrate(0);\n }\n }\n\n destroy(): void {\n this.stopPattern();\n if (this.hapticLabel) {\n this.hapticLabel.remove();\n this.hapticLabel = null;\n this.domInitialized = false;\n }\n if (this.audioCtx) {\n this.audioCtx.close();\n this.audioCtx = null;\n this.audioFilter = null;\n this.audioGain = null;\n this.audioBuffer = null;\n }\n }\n\n setDebug(debug: boolean): void {\n this.debug = debug;\n if (!debug && this.audioCtx) {\n this.audioCtx.close();\n this.audioCtx = null;\n this.audioFilter = null;\n this.audioGain = null;\n this.audioBuffer = null;\n }\n }\n\n setShowSwitch(show: boolean): void {\n this.showSwitch = show;\n if (this.hapticLabel) {\n const checkbox = this.hapticLabel.querySelector(\"input\");\n this.hapticLabel.style.display = show ? \"\" : \"none\";\n if (checkbox) checkbox.style.display = show ? \"\" : \"none\";\n }\n }\n\n private stopPattern(): void {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n this.patternResolve?.();\n this.patternResolve = null;\n }\n\n private runPattern(\n vibrations: Vibration[],\n defaultIntensity: number,\n ): Promise<void> {\n return new Promise((resolve) => {\n this.patternResolve = resolve;\n\n // Build phase boundaries: each vibration has an optional delay then an \"on\" phase\n const phases: { end: number; isOn: boolean; intensity: number }[] = [];\n let cumulative = 0;\n for (const vib of vibrations) {\n const intensity = Math.max(\n 0,\n Math.min(1, vib.intensity ?? defaultIntensity),\n );\n const delay = vib.delay ?? 0;\n if (delay > 0) {\n cumulative += delay;\n phases.push({ end: cumulative, isOn: false, intensity: 0 });\n }\n cumulative += vib.duration;\n phases.push({ end: cumulative, isOn: true, intensity });\n }\n const totalDuration = cumulative;\n\n let startTime = 0;\n let lastToggleTime = -1;\n\n const loop = (time: number) => {\n if (startTime === 0) startTime = time;\n const elapsed = time - startTime;\n\n if (elapsed >= totalDuration) {\n this.rafId = null;\n this.patternResolve = null;\n resolve();\n return;\n }\n\n // Find current phase\n let phase = phases[0]!;\n for (const p of phases) {\n if (elapsed < p.end) {\n phase = p;\n break;\n }\n }\n\n if (phase.isOn) {\n const toggleInterval =\n TOGGLE_MIN + (1 - phase.intensity) * TOGGLE_MAX;\n\n if (lastToggleTime === -1) {\n lastToggleTime = time;\n } else if (time - lastToggleTime >= toggleInterval) {\n this.hapticLabel?.click();\n if (this.debug && this.audioCtx) {\n this.playClick(phase.intensity);\n }\n lastToggleTime = time;\n }\n }\n\n this.rafId = requestAnimationFrame(loop);\n };\n this.rafId = requestAnimationFrame(loop);\n });\n }\n\n private playClick(intensity: number): void {\n if (\n !this.audioCtx ||\n !this.audioFilter ||\n !this.audioGain ||\n !this.audioBuffer\n )\n return;\n\n const data = this.audioBuffer.getChannelData(0);\n for (let i = 0; i < data.length; i++) {\n data[i] = (Math.random() * 2 - 1) * Math.exp(-i / 25);\n }\n\n this.audioGain.gain.value = 0.5 * intensity;\n\n const source = this.audioCtx.createBufferSource();\n source.buffer = this.audioBuffer;\n source.connect(this.audioFilter);\n source.onended = () => source.disconnect();\n source.start();\n }\n\n private async ensureAudio(): Promise<void> {\n if (!this.audioCtx && typeof AudioContext !== \"undefined\") {\n this.audioCtx = new AudioContext();\n\n this.audioFilter = this.audioCtx.createBiquadFilter();\n this.audioFilter.type = \"bandpass\";\n this.audioFilter.frequency.value = 4000;\n this.audioFilter.Q.value = 8;\n\n this.audioGain = this.audioCtx.createGain();\n this.audioFilter.connect(this.audioGain);\n this.audioGain.connect(this.audioCtx.destination);\n\n const duration = 0.004;\n this.audioBuffer = this.audioCtx.createBuffer(\n 1,\n this.audioCtx.sampleRate * duration,\n this.audioCtx.sampleRate,\n );\n const data = this.audioBuffer.getChannelData(0);\n for (let i = 0; i < data.length; i++) {\n data[i] = (Math.random() * 2 - 1) * Math.exp(-i / 25);\n }\n }\n if (this.audioCtx?.state === \"suspended\") {\n await this.audioCtx.resume();\n }\n }\n\n private ensureDOM(): void {\n if (this.domInitialized) return;\n if (typeof document === \"undefined\") return;\n\n const id = `web-haptics-${this.instanceId}`;\n\n const hapticLabel = document.createElement(\"label\");\n hapticLabel.setAttribute(\"for\", id);\n hapticLabel.textContent = \"Haptic feedback\";\n hapticLabel.style.position = \"fixed\";\n hapticLabel.style.bottom = \"10px\";\n hapticLabel.style.left = \"10px\";\n hapticLabel.style.padding = \"5px 10px\";\n hapticLabel.style.backgroundColor = \"rgba(0, 0, 0, 0.7)\";\n hapticLabel.style.color = \"white\";\n hapticLabel.style.fontFamily = \"sans-serif\";\n hapticLabel.style.fontSize = \"14px\";\n hapticLabel.style.borderRadius = \"4px\";\n hapticLabel.style.zIndex = \"9999\";\n hapticLabel.style.userSelect = \"none\";\n this.hapticLabel = hapticLabel;\n\n const hapticCheckbox = document.createElement(\"input\");\n hapticCheckbox.type = \"checkbox\";\n hapticCheckbox.setAttribute(\"switch\", \"\");\n hapticCheckbox.id = id;\n hapticCheckbox.style.all = \"initial\";\n hapticCheckbox.style.appearance = \"auto\";\n\n if (!this.showSwitch) {\n hapticLabel.style.display = \"none\";\n hapticCheckbox.style.display = \"none\";\n }\n\n hapticLabel.appendChild(hapticCheckbox);\n document.body.appendChild(hapticLabel);\n this.domInitialized = true;\n }\n}\n"],"mappings":"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","names":["defaultPatterns","TOGGLE_MIN","TOGGLE_MAX","MAX_PHASE_MS","PWM_CYCLE","normalizeInput","input","preset","defaultPatterns","v","nums","vibrations","i","delay","modulateVibration","duration","intensity","onTime","offTime","result","remaining","remOn","remOff","toVibratePattern","defaultIntensity","vib","modulated","seg","instanceCounter","WebHaptics","_WebHaptics","options","normalized","firstIntensity","debug","show","checkbox","resolve","phases","cumulative","totalDuration","startTime","lastToggleTime","loop","time","elapsed","phase","p","toggleInterval","data","source","id","hapticLabel","hapticCheckbox"]}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
|
-
import { W as WebHapticsOptions, H as HapticInput, T as TriggerOptions } from './types-
|
|
2
|
-
export { a as HapticPattern, b as HapticPreset } from './types-
|
|
1
|
+
import { W as WebHapticsOptions, H as HapticInput, T as TriggerOptions } from './types-DkzcH7AA.mjs';
|
|
2
|
+
export { a as HapticPattern, b as HapticPreset, V as Vibration } from './types-DkzcH7AA.mjs';
|
|
3
3
|
|
|
4
|
-
var version = "0.0.
|
|
4
|
+
var version = "0.0.3";
|
|
5
5
|
|
|
6
6
|
declare class WebHaptics {
|
|
7
7
|
private hapticLabel;
|
|
8
8
|
private domInitialized;
|
|
9
9
|
private instanceId;
|
|
10
10
|
private debug;
|
|
11
|
+
private showSwitch;
|
|
11
12
|
private rafId;
|
|
12
13
|
private patternResolve;
|
|
13
14
|
private audioCtx;
|
|
14
15
|
private audioFilter;
|
|
15
16
|
private audioGain;
|
|
17
|
+
private audioBuffer;
|
|
16
18
|
constructor(options?: WebHapticsOptions);
|
|
17
19
|
static readonly isSupported: boolean;
|
|
18
20
|
trigger(input?: HapticInput, options?: TriggerOptions): Promise<void>;
|
|
19
21
|
cancel(): void;
|
|
20
22
|
destroy(): void;
|
|
21
23
|
setDebug(debug: boolean): void;
|
|
24
|
+
setShowSwitch(show: boolean): void;
|
|
22
25
|
private stopPattern;
|
|
23
26
|
private runPattern;
|
|
24
27
|
private playClick;
|
|
@@ -28,24 +31,46 @@ declare class WebHaptics {
|
|
|
28
31
|
|
|
29
32
|
declare const defaultPatterns: {
|
|
30
33
|
readonly success: {
|
|
31
|
-
readonly pattern: [
|
|
34
|
+
readonly pattern: [{
|
|
35
|
+
readonly duration: 50;
|
|
36
|
+
}, {
|
|
37
|
+
readonly delay: 50;
|
|
38
|
+
readonly duration: 50;
|
|
39
|
+
}];
|
|
32
40
|
readonly description: "A series of taps indicating success.";
|
|
33
|
-
readonly intensity: 0.5;
|
|
34
41
|
};
|
|
35
42
|
readonly nudge: {
|
|
36
|
-
readonly pattern: [
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
readonly pattern: [{
|
|
44
|
+
readonly duration: 80;
|
|
45
|
+
readonly intensity: 0.8;
|
|
46
|
+
}, {
|
|
47
|
+
readonly delay: 80;
|
|
48
|
+
readonly duration: 50;
|
|
49
|
+
readonly intensity: 0.3;
|
|
50
|
+
}];
|
|
51
|
+
readonly description: "A series of taps indicating a nudge.";
|
|
39
52
|
};
|
|
40
53
|
readonly error: {
|
|
41
|
-
readonly pattern: [
|
|
42
|
-
|
|
43
|
-
|
|
54
|
+
readonly pattern: [{
|
|
55
|
+
readonly duration: 50;
|
|
56
|
+
readonly intensity: 0.75;
|
|
57
|
+
}, {
|
|
58
|
+
readonly delay: 50;
|
|
59
|
+
readonly duration: 50;
|
|
60
|
+
readonly intensity: 0.75;
|
|
61
|
+
}, {
|
|
62
|
+
readonly delay: 50;
|
|
63
|
+
readonly duration: 50;
|
|
64
|
+
readonly intensity: 0.75;
|
|
65
|
+
}];
|
|
66
|
+
readonly description: "A series of taps indicating a warning or error.";
|
|
44
67
|
};
|
|
45
|
-
readonly
|
|
46
|
-
readonly pattern: [
|
|
68
|
+
readonly buzz: {
|
|
69
|
+
readonly pattern: [{
|
|
70
|
+
readonly duration: 1000;
|
|
71
|
+
readonly intensity: 1;
|
|
72
|
+
}];
|
|
47
73
|
readonly description: "A long vibration.";
|
|
48
|
-
readonly intensity: 1;
|
|
49
74
|
};
|
|
50
75
|
};
|
|
51
76
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
|
-
import { W as WebHapticsOptions, H as HapticInput, T as TriggerOptions } from './types-
|
|
2
|
-
export { a as HapticPattern, b as HapticPreset } from './types-
|
|
1
|
+
import { W as WebHapticsOptions, H as HapticInput, T as TriggerOptions } from './types-DkzcH7AA.js';
|
|
2
|
+
export { a as HapticPattern, b as HapticPreset, V as Vibration } from './types-DkzcH7AA.js';
|
|
3
3
|
|
|
4
|
-
var version = "0.0.
|
|
4
|
+
var version = "0.0.3";
|
|
5
5
|
|
|
6
6
|
declare class WebHaptics {
|
|
7
7
|
private hapticLabel;
|
|
8
8
|
private domInitialized;
|
|
9
9
|
private instanceId;
|
|
10
10
|
private debug;
|
|
11
|
+
private showSwitch;
|
|
11
12
|
private rafId;
|
|
12
13
|
private patternResolve;
|
|
13
14
|
private audioCtx;
|
|
14
15
|
private audioFilter;
|
|
15
16
|
private audioGain;
|
|
17
|
+
private audioBuffer;
|
|
16
18
|
constructor(options?: WebHapticsOptions);
|
|
17
19
|
static readonly isSupported: boolean;
|
|
18
20
|
trigger(input?: HapticInput, options?: TriggerOptions): Promise<void>;
|
|
19
21
|
cancel(): void;
|
|
20
22
|
destroy(): void;
|
|
21
23
|
setDebug(debug: boolean): void;
|
|
24
|
+
setShowSwitch(show: boolean): void;
|
|
22
25
|
private stopPattern;
|
|
23
26
|
private runPattern;
|
|
24
27
|
private playClick;
|
|
@@ -28,24 +31,46 @@ declare class WebHaptics {
|
|
|
28
31
|
|
|
29
32
|
declare const defaultPatterns: {
|
|
30
33
|
readonly success: {
|
|
31
|
-
readonly pattern: [
|
|
34
|
+
readonly pattern: [{
|
|
35
|
+
readonly duration: 50;
|
|
36
|
+
}, {
|
|
37
|
+
readonly delay: 50;
|
|
38
|
+
readonly duration: 50;
|
|
39
|
+
}];
|
|
32
40
|
readonly description: "A series of taps indicating success.";
|
|
33
|
-
readonly intensity: 0.5;
|
|
34
41
|
};
|
|
35
42
|
readonly nudge: {
|
|
36
|
-
readonly pattern: [
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
readonly pattern: [{
|
|
44
|
+
readonly duration: 80;
|
|
45
|
+
readonly intensity: 0.8;
|
|
46
|
+
}, {
|
|
47
|
+
readonly delay: 80;
|
|
48
|
+
readonly duration: 50;
|
|
49
|
+
readonly intensity: 0.3;
|
|
50
|
+
}];
|
|
51
|
+
readonly description: "A series of taps indicating a nudge.";
|
|
39
52
|
};
|
|
40
53
|
readonly error: {
|
|
41
|
-
readonly pattern: [
|
|
42
|
-
|
|
43
|
-
|
|
54
|
+
readonly pattern: [{
|
|
55
|
+
readonly duration: 50;
|
|
56
|
+
readonly intensity: 0.75;
|
|
57
|
+
}, {
|
|
58
|
+
readonly delay: 50;
|
|
59
|
+
readonly duration: 50;
|
|
60
|
+
readonly intensity: 0.75;
|
|
61
|
+
}, {
|
|
62
|
+
readonly delay: 50;
|
|
63
|
+
readonly duration: 50;
|
|
64
|
+
readonly intensity: 0.75;
|
|
65
|
+
}];
|
|
66
|
+
readonly description: "A series of taps indicating a warning or error.";
|
|
44
67
|
};
|
|
45
|
-
readonly
|
|
46
|
-
readonly pattern: [
|
|
68
|
+
readonly buzz: {
|
|
69
|
+
readonly pattern: [{
|
|
70
|
+
readonly duration: 1000;
|
|
71
|
+
readonly intensity: 1;
|
|
72
|
+
}];
|
|
47
73
|
readonly description: "A long vibration.";
|
|
48
|
-
readonly intensity: 1;
|
|
49
74
|
};
|
|
50
75
|
};
|
|
51
76
|
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
"use strict";var m=Object.defineProperty;var
|
|
2
|
+
"use strict";var m=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var C=Object.getOwnPropertyNames;var w=Object.prototype.hasOwnProperty;var I=(s,e)=>{for(var t in e)m(s,t,{get:e[t],enumerable:!0})},M=(s,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of C(e))!w.call(s,n)&&n!==t&&m(s,n,{get:()=>e[n],enumerable:!(i=x(e,n))||i.enumerable});return s};var A=s=>M(m({},"__esModule",{value:!0}),s);var G={};I(G,{WebHaptics:()=>b,defaultPatterns:()=>h,version:()=>v});module.exports=A(G);var v="0.0.3";var h={success:{pattern:[{duration:50},{delay:50,duration:50}],description:"A series of taps indicating success."},nudge:{pattern:[{duration:80,intensity:.8},{delay:80,duration:50,intensity:.3}],description:"A series of taps indicating a nudge."},error:{pattern:[{duration:50,intensity:.75},{delay:50,duration:50,intensity:.75},{delay:50,duration:50,intensity:.75}],description:"A series of taps indicating a warning or error."},buzz:{pattern:[{duration:1e3,intensity:1}],description:"A long vibration."}};var L=16,k=184,g=1e3,f=20;function F(s){if(typeof s=="number")return{vibrations:[{duration:s}]};if(typeof s=="string"){let e=h[s];return e?{vibrations:e.pattern.map(t=>({...t}))}:(console.warn(`[web-haptics] Unknown preset: "${s}"`),null)}if(Array.isArray(s)){if(s.length===0)return{vibrations:[]};if(typeof s[0]=="number"){let e=s,t=[];for(let i=0;i<e.length;i+=2){let n=i>0?e[i-1]:0;t.push({...n>0&&{delay:n},duration:e[i]})}return{vibrations:t}}return{vibrations:s.map(e=>({...e}))}}return{vibrations:s.pattern.map(e=>({...e}))}}function S(s,e){if(e>=1)return[s];if(e<=0)return[];let t=Math.max(1,Math.round(f*e)),i=f-t,n=[],r=s;for(;r>=f;)n.push(t),n.push(i),r-=f;if(r>0){let a=Math.max(1,Math.round(r*e));n.push(a);let o=r-a;o>0&&n.push(o)}return n}function O(s,e){let t=[];for(let i=0;i<s.length;i++){let n=s[i],r=Math.max(0,Math.min(1,n.intensity??e)),a=n.delay??0;a>0&&(t.length>0&&t.length%2===0?t[t.length-1]+=a:(t.length===0&&t.push(0),t.push(a)));let o=S(n.duration,r);if(o.length===0){t.length>0&&t.length%2===0?t[t.length-1]+=n.duration:n.duration>0&&(t.push(0),t.push(n.duration));continue}for(let d of o)t.push(d)}return t}var H=0,b=class s{hapticLabel=null;domInitialized=!1;instanceId;debug;showSwitch;rafId=null;patternResolve=null;audioCtx=null;audioFilter=null;audioGain=null;audioBuffer=null;constructor(e){this.instanceId=++H,this.debug=e?.debug??!1,this.showSwitch=e?.showSwitch??!1}static isSupported=typeof navigator<"u"&&typeof navigator.vibrate=="function";async trigger(e=[{duration:10}],t){let i=F(e);if(!i)return;let{vibrations:n}=i;if(n.length===0)return;let r=Math.max(0,Math.min(1,t?.intensity??.5));for(let a of n)if(a.duration>g&&(a.duration=g),!Number.isFinite(a.duration)||a.duration<0||a.delay!==void 0&&(!Number.isFinite(a.delay)||a.delay<0)){console.warn("[web-haptics] Invalid vibration values. Durations and delays must be finite non-negative numbers.");return}if(s.isSupported&&navigator.vibrate(O(n,r)),!s.isSupported||this.debug){if(this.ensureDOM(),!this.hapticLabel)return;if(this.debug&&await this.ensureAudio(),this.stopPattern(),this.hapticLabel.click(),this.debug&&this.audioCtx){let a=Math.max(0,Math.min(1,n[0].intensity??r));this.playClick(a)}await this.runPattern(n,r)}}cancel(){this.stopPattern(),s.isSupported&&navigator.vibrate(0)}destroy(){this.stopPattern(),this.hapticLabel&&(this.hapticLabel.remove(),this.hapticLabel=null,this.domInitialized=!1),this.audioCtx&&(this.audioCtx.close(),this.audioCtx=null,this.audioFilter=null,this.audioGain=null,this.audioBuffer=null)}setDebug(e){this.debug=e,!e&&this.audioCtx&&(this.audioCtx.close(),this.audioCtx=null,this.audioFilter=null,this.audioGain=null,this.audioBuffer=null)}setShowSwitch(e){if(this.showSwitch=e,this.hapticLabel){let t=this.hapticLabel.querySelector("input");this.hapticLabel.style.display=e?"":"none",t&&(t.style.display=e?"":"none")}}stopPattern(){this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.patternResolve?.(),this.patternResolve=null}runPattern(e,t){return new Promise(i=>{this.patternResolve=i;let n=[],r=0;for(let l of e){let p=Math.max(0,Math.min(1,l.intensity??t)),u=l.delay??0;u>0&&(r+=u,n.push({end:r,isOn:!1,intensity:0})),r+=l.duration,n.push({end:r,isOn:!0,intensity:p})}let a=r,o=0,d=-1,y=l=>{o===0&&(o=l);let p=l-o;if(p>=a){this.rafId=null,this.patternResolve=null,i();return}let u=n[0];for(let c of n)if(p<c.end){u=c;break}if(u.isOn){let c=L+(1-u.intensity)*k;d===-1?d=l:l-d>=c&&(this.hapticLabel?.click(),this.debug&&this.audioCtx&&this.playClick(u.intensity),d=l)}this.rafId=requestAnimationFrame(y)};this.rafId=requestAnimationFrame(y)})}playClick(e){if(!this.audioCtx||!this.audioFilter||!this.audioGain||!this.audioBuffer)return;let t=this.audioBuffer.getChannelData(0);for(let n=0;n<t.length;n++)t[n]=(Math.random()*2-1)*Math.exp(-n/25);this.audioGain.gain.value=.5*e;let i=this.audioCtx.createBufferSource();i.buffer=this.audioBuffer,i.connect(this.audioFilter),i.onended=()=>i.disconnect(),i.start()}async ensureAudio(){if(!this.audioCtx&&typeof AudioContext<"u"){this.audioCtx=new AudioContext,this.audioFilter=this.audioCtx.createBiquadFilter(),this.audioFilter.type="bandpass",this.audioFilter.frequency.value=4e3,this.audioFilter.Q.value=8,this.audioGain=this.audioCtx.createGain(),this.audioFilter.connect(this.audioGain),this.audioGain.connect(this.audioCtx.destination);let e=.004;this.audioBuffer=this.audioCtx.createBuffer(1,this.audioCtx.sampleRate*e,this.audioCtx.sampleRate);let t=this.audioBuffer.getChannelData(0);for(let i=0;i<t.length;i++)t[i]=(Math.random()*2-1)*Math.exp(-i/25)}this.audioCtx?.state==="suspended"&&await this.audioCtx.resume()}ensureDOM(){if(this.domInitialized||typeof document>"u")return;let e=`web-haptics-${this.instanceId}`,t=document.createElement("label");t.setAttribute("for",e),t.textContent="Haptic feedback",t.style.position="fixed",t.style.bottom="10px",t.style.left="10px",t.style.padding="5px 10px",t.style.backgroundColor="rgba(0, 0, 0, 0.7)",t.style.color="white",t.style.fontFamily="sans-serif",t.style.fontSize="14px",t.style.borderRadius="4px",t.style.zIndex="9999",t.style.userSelect="none",this.hapticLabel=t;let i=document.createElement("input");i.type="checkbox",i.setAttribute("switch",""),i.id=e,i.style.all="initial",i.style.appearance="auto",this.showSwitch||(t.style.display="none",i.style.display="none"),t.appendChild(i),document.body.appendChild(t),this.domInitialized=!0}};0&&(module.exports={WebHaptics,defaultPatterns,version});
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../package.json","../src/lib/web-haptics/patterns.ts","../src/lib/web-haptics/index.ts"],"sourcesContent":["export { version } from \"./../package.json\";\n\nexport { WebHaptics } from \"./lib/web-haptics\";\nexport { defaultPatterns } from \"./lib/web-haptics/patterns\";\nexport type {\n HapticPattern,\n HapticPreset,\n HapticInput,\n TriggerOptions,\n WebHapticsOptions,\n} from \"./lib/web-haptics/types\";\n","{\n \"name\": \"web-haptics\",\n \"version\": \"0.0.1\",\n \"description\": \"Haptic feedback for the mobile web.\",\n \"homepage\": \"https://haptics.lochie.me\",\n \"sideEffects\": false,\n \"author\": \"Lochie Axon\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/lochie/web-haptics.git\",\n \"directory\": \"packages/web-haptics\"\n },\n \"types\": \"dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.mjs\",\n \"require\": \"./dist/index.js\"\n },\n \"./react\": {\n \"types\": \"./dist/react/index.d.ts\",\n \"import\": \"./dist/react/index.mjs\",\n \"require\": \"./dist/react/index.js\"\n },\n \"./vue\": {\n \"types\": \"./dist/vue/index.d.ts\",\n \"import\": \"./dist/vue/index.mjs\",\n \"require\": \"./dist/vue/index.js\"\n },\n \"./svelte\": {\n \"types\": \"./dist/svelte/index.d.ts\",\n \"import\": \"./dist/svelte/index.mjs\",\n \"require\": \"./dist/svelte/index.js\"\n }\n },\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"lint\": \"eslint -c .eslintrc.cjs ./src/**/*.{ts,tsx}\",\n \"lint:fix\": \"eslint --fix -c .eslintrc.cjs ./src/**/*.{ts,tsx}\",\n \"pre-commit\": \"lint-staged\",\n \"prepublishOnly\": \"pnpm build\"\n },\n \"keywords\": [\n \"haptics\",\n \"vibration\",\n \"feedback\",\n \"web\",\n \"mobile\",\n \"react\",\n \"vue\",\n \"svelte\",\n \"typescript\"\n ],\n \"bugs\": {\n \"url\": \"https://github.com/lochie/web-haptics/issues\"\n },\n \"files\": [\n \"dist/*\",\n \"LICENSE\",\n \"README.md\"\n ],\n \"peerDependencies\": {\n \"react\": \">=18\",\n \"react-dom\": \">=18\",\n \"vue\": \">=3\",\n \"svelte\": \">=4\"\n },\n \"peerDependenciesMeta\": {\n \"react\": {\n \"optional\": true\n },\n \"react-dom\": {\n \"optional\": true\n },\n \"vue\": {\n \"optional\": true\n },\n \"svelte\": {\n \"optional\": true\n }\n },\n \"devDependencies\": {\n \"@types/react\": \"^18.2.15\",\n \"@typescript-eslint/eslint-plugin\": \"^6.8.0\",\n \"@typescript-eslint/parser\": \"^6.8.0\",\n \"eslint\": \"^9.36.0\",\n \"eslint-config-airbnb\": \"^19.0.4\",\n \"eslint-config-airbnb-typescript\": \"^17.1.0\",\n \"eslint-config-prettier\": \"^9.0.0\",\n \"eslint-plugin-import\": \"^2.28.1\",\n \"eslint-plugin-jsx-a11y\": \"^6.7.1\",\n \"eslint-plugin-prettier\": \"^5.0.1\",\n \"eslint-plugin-react\": \"^7.33.2\",\n \"eslint-plugin-react-hooks\": \"^4.6.0\",\n \"prettier\": \"^3.0.3\",\n \"react\": \"^18.2.0\",\n \"react-dom\": \"^18.2.0\",\n \"svelte\": \"^4.0.0\",\n \"tsup\": \"^8.5.0\",\n \"typescript\": \"^5.9.3\",\n \"vue\": \"^3.3.0\"\n }\n}\n","import type { HapticPreset } from \"./types\";\n\nexport const defaultPatterns = {\n success: {\n pattern: [50, 50, 50],\n description: \"A series of taps indicating success.\",\n intensity: 0.5,\n },\n nudge: {\n pattern: [80, 100, 200],\n description: \"A series of taps indicating a warning.\",\n intensity: 0.5,\n },\n error: {\n pattern: [50, 50, 50, 50, 50],\n description: \"A series of taps indicating an error.\",\n intensity: 0.75,\n },\n long: {\n pattern: [1000],\n description: \"A long vibration.\",\n intensity: 1,\n },\n} as const satisfies Record<string, HapticPreset>;\n","import { defaultPatterns } from \"./patterns\";\nimport type { HapticInput, TriggerOptions, WebHapticsOptions } from \"./types\";\n\nconst TOGGLE_MIN = 16; // ms at intensity 1 (every frame)\nconst TOGGLE_MAX = 184; // range above min (0.5 intensity ≈ 100ms)\nconst MAX_PHASE_MS = 1000; // browser haptic window limit\nconst PWM_CYCLE = 20; // ms per intensity modulation cycle\n\nfunction modulatePattern(pattern: number[], intensity: number): number[] {\n if (intensity >= 1) return pattern;\n if (intensity <= 0) return [];\n\n const onTime = Math.max(1, Math.round(PWM_CYCLE * intensity));\n const offTime = PWM_CYCLE - onTime;\n const result: number[] = [];\n\n for (let i = 0; i < pattern.length; i++) {\n const dur = pattern[i]!;\n\n if (i % 2 === 0) {\n let remaining = dur;\n while (remaining >= PWM_CYCLE) {\n result.push(onTime);\n result.push(offTime);\n remaining -= PWM_CYCLE;\n }\n if (remaining > 0) {\n const remOn = Math.max(1, Math.round(remaining * intensity));\n result.push(remOn);\n const remOff = remaining - remOn;\n if (remOff > 0) result.push(remOff);\n }\n } else {\n if (result.length > 0 && result.length % 2 === 0) {\n result[result.length - 1]! += dur;\n } else {\n result.push(dur);\n }\n }\n }\n\n return result;\n}\n\nlet instanceCounter = 0;\n\nexport class WebHaptics {\n private hapticLabel: HTMLLabelElement | null = null;\n private domInitialized = false;\n private instanceId: number;\n private debug: boolean;\n private rafId: number | null = null;\n private patternResolve: (() => void) | null = null;\n private audioCtx: AudioContext | null = null;\n private audioFilter: BiquadFilterNode | null = null;\n private audioGain: GainNode | null = null;\n\n constructor(options?: WebHapticsOptions) {\n this.instanceId = ++instanceCounter;\n this.debug = options?.debug ?? false;\n }\n\n static readonly isSupported: boolean =\n typeof navigator !== \"undefined\" &&\n typeof navigator.vibrate === \"function\";\n\n async trigger(\n input: HapticInput = [10],\n options?: TriggerOptions,\n ): Promise<void> {\n let pattern: number[];\n let defaultIntensity = 0.5;\n\n if (typeof input === \"number\") {\n pattern = [input];\n } else if (typeof input === \"string\") {\n const preset =\n defaultPatterns[input as keyof typeof defaultPatterns];\n if (!preset) {\n console.warn(`[web-haptics] Unknown preset: \"${input}\"`);\n return;\n }\n pattern = [...preset.pattern];\n defaultIntensity = preset.intensity;\n } else if (Array.isArray(input)) {\n pattern = [...input];\n } else {\n pattern = [...input.pattern];\n defaultIntensity = input.intensity;\n }\n\n const intensity = Math.max(\n 0,\n Math.min(1, options?.intensity ?? defaultIntensity),\n );\n\n for (let i = 0; i < pattern.length; i++) {\n if (i % 2 === 0 && pattern[i]! > MAX_PHASE_MS) {\n pattern[i] = MAX_PHASE_MS;\n }\n if (!Number.isFinite(pattern[i]) || pattern[i]! < 0) {\n console.warn(\n `[web-haptics] Invalid value at index ${i}: ${pattern[i]}. Pattern values must be finite non-negative numbers.`,\n );\n return;\n }\n }\n\n if (WebHaptics.isSupported) {\n navigator.vibrate(modulatePattern(pattern, intensity));\n }\n\n if (!WebHaptics.isSupported || this.debug) {\n this.ensureDOM();\n if (!this.hapticLabel) return;\n\n if (this.debug) {\n await this.ensureAudio();\n }\n\n this.stopPattern();\n await this.runPattern(pattern, intensity);\n }\n }\n\n cancel(): void {\n this.stopPattern();\n if (WebHaptics.isSupported) {\n navigator.vibrate(0);\n }\n }\n\n destroy(): void {\n this.stopPattern();\n if (this.hapticLabel) {\n this.hapticLabel.remove();\n this.hapticLabel = null;\n this.domInitialized = false;\n }\n if (this.audioCtx) {\n this.audioCtx.close();\n this.audioCtx = null;\n this.audioFilter = null;\n this.audioGain = null;\n }\n }\n\n setDebug(debug: boolean): void {\n this.debug = debug;\n if (!debug && this.audioCtx) {\n this.audioCtx.close();\n this.audioCtx = null;\n this.audioFilter = null;\n this.audioGain = null;\n }\n }\n\n private stopPattern(): void {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n this.patternResolve?.();\n this.patternResolve = null;\n }\n\n private runPattern(pattern: number[], intensity: number): Promise<void> {\n return new Promise((resolve) => {\n this.patternResolve = resolve;\n\n const phases: number[] = [];\n let cumulative = 0;\n for (const p of pattern) {\n cumulative += p;\n phases.push(cumulative);\n }\n const totalDuration = cumulative;\n\n const toggleInterval = TOGGLE_MIN + (1 - intensity) * TOGGLE_MAX;\n let startTime = 0;\n let lastToggleTime = 0;\n\n const loop = (time: number) => {\n if (startTime === 0) startTime = time;\n const elapsed = time - startTime;\n\n if (elapsed >= totalDuration) {\n this.rafId = null;\n this.patternResolve = null;\n resolve();\n return;\n }\n\n let phaseIndex = 0;\n for (let i = 0; i < phases.length; i++) {\n if (elapsed < phases[i]!) {\n phaseIndex = i;\n break;\n }\n }\n\n if (phaseIndex % 2 === 0) {\n if (time - lastToggleTime >= toggleInterval) {\n this.hapticLabel?.click();\n if (this.debug && this.audioCtx) {\n this.playClick(intensity);\n }\n lastToggleTime = time;\n }\n }\n\n this.rafId = requestAnimationFrame(loop);\n };\n this.rafId = requestAnimationFrame(loop);\n });\n }\n\n private playClick(intensity: number): void {\n if (!this.audioCtx || !this.audioFilter || !this.audioGain) return;\n\n const duration = 0.004;\n const buffer = this.audioCtx.createBuffer(\n 1,\n this.audioCtx.sampleRate * duration,\n this.audioCtx.sampleRate,\n );\n const data = buffer.getChannelData(0);\n for (let i = 0; i < data.length; i++) {\n data[i] = (Math.random() * 2 - 1) * Math.exp(-i / 25);\n }\n\n this.audioGain.gain.value = 0.5 * intensity;\n\n const source = this.audioCtx.createBufferSource();\n source.buffer = buffer;\n source.connect(this.audioFilter);\n source.start();\n }\n\n private async ensureAudio(): Promise<void> {\n if (!this.audioCtx && typeof AudioContext !== \"undefined\") {\n this.audioCtx = new AudioContext();\n\n this.audioFilter = this.audioCtx.createBiquadFilter();\n this.audioFilter.type = \"bandpass\";\n this.audioFilter.frequency.value = 4000;\n this.audioFilter.Q.value = 8;\n\n this.audioGain = this.audioCtx.createGain();\n this.audioFilter.connect(this.audioGain);\n this.audioGain.connect(this.audioCtx.destination);\n }\n if (this.audioCtx?.state === \"suspended\") {\n await this.audioCtx.resume();\n }\n }\n\n private ensureDOM(): void {\n if (this.domInitialized) return;\n if (typeof document === \"undefined\") return;\n\n const id = `web-haptics-${this.instanceId}`;\n\n const hapticLabel = document.createElement(\"label\");\n hapticLabel.setAttribute(\"for\", id);\n hapticLabel.textContent = \"Haptic feedback\";\n this.hapticLabel = hapticLabel;\n\n const hapticCheckbox = document.createElement(\"input\");\n hapticCheckbox.type = \"checkbox\";\n hapticCheckbox.setAttribute(\"switch\", \"\");\n hapticCheckbox.id = id;\n\n hapticLabel.style.display = \"none\";\n hapticCheckbox.style.display = \"none\";\n\n hapticLabel.appendChild(hapticCheckbox);\n document.body.appendChild(hapticLabel);\n this.domInitialized = true;\n }\n}\n"],"mappings":"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","names":["src_exports","__export","WebHaptics","defaultPatterns","version","__toCommonJS","version","defaultPatterns","TOGGLE_MIN","TOGGLE_MAX","MAX_PHASE_MS","PWM_CYCLE","modulatePattern","pattern","intensity","onTime","offTime","result","i","dur","remaining","remOn","remOff","instanceCounter","WebHaptics","_WebHaptics","options","input","defaultIntensity","preset","defaultPatterns","debug","resolve","phases","cumulative","p","totalDuration","toggleInterval","startTime","lastToggleTime","loop","time","elapsed","phaseIndex","buffer","data","source","id","hapticLabel","hapticCheckbox"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../package.json","../src/lib/web-haptics/patterns.ts","../src/lib/web-haptics/index.ts"],"sourcesContent":["export { version } from \"./../package.json\";\n\nexport { WebHaptics } from \"./lib/web-haptics\";\nexport { defaultPatterns } from \"./lib/web-haptics/patterns\";\nexport type {\n Vibration,\n HapticPattern,\n HapticPreset,\n HapticInput,\n TriggerOptions,\n WebHapticsOptions,\n} from \"./lib/web-haptics/types\";\n","{\n \"name\": \"web-haptics\",\n \"version\": \"0.0.3\",\n \"description\": \"Haptic feedback for the mobile web.\",\n \"homepage\": \"https://haptics.lochie.me\",\n \"sideEffects\": false,\n \"author\": \"Lochie Axon\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/lochie/web-haptics.git\",\n \"directory\": \"packages/web-haptics\"\n },\n \"types\": \"dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.mjs\",\n \"require\": \"./dist/index.js\"\n },\n \"./react\": {\n \"types\": \"./dist/react/index.d.ts\",\n \"import\": \"./dist/react/index.mjs\",\n \"require\": \"./dist/react/index.js\"\n },\n \"./vue\": {\n \"types\": \"./dist/vue/index.d.ts\",\n \"import\": \"./dist/vue/index.mjs\",\n \"require\": \"./dist/vue/index.js\"\n },\n \"./svelte\": {\n \"types\": \"./dist/svelte/index.d.ts\",\n \"import\": \"./dist/svelte/index.mjs\",\n \"require\": \"./dist/svelte/index.js\"\n }\n },\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"lint\": \"eslint -c .eslintrc.cjs ./src/**/*.{ts,tsx}\",\n \"lint:fix\": \"eslint --fix -c .eslintrc.cjs ./src/**/*.{ts,tsx}\",\n \"pre-commit\": \"lint-staged\",\n \"prepublishOnly\": \"pnpm build\"\n },\n \"keywords\": [\n \"haptics\",\n \"vibration\",\n \"feedback\",\n \"web\",\n \"mobile\",\n \"react\",\n \"vue\",\n \"svelte\",\n \"typescript\"\n ],\n \"bugs\": {\n \"url\": \"https://github.com/lochie/web-haptics/issues\"\n },\n \"files\": [\n \"dist/*\",\n \"LICENSE\",\n \"README.md\"\n ],\n \"peerDependencies\": {\n \"react\": \">=18\",\n \"react-dom\": \">=18\",\n \"vue\": \">=3\",\n \"svelte\": \">=4\"\n },\n \"peerDependenciesMeta\": {\n \"react\": {\n \"optional\": true\n },\n \"react-dom\": {\n \"optional\": true\n },\n \"vue\": {\n \"optional\": true\n },\n \"svelte\": {\n \"optional\": true\n }\n },\n \"devDependencies\": {\n \"@types/react\": \"^18.2.15\",\n \"@typescript-eslint/eslint-plugin\": \"^6.8.0\",\n \"@typescript-eslint/parser\": \"^6.8.0\",\n \"eslint\": \"^9.36.0\",\n \"eslint-config-airbnb\": \"^19.0.4\",\n \"eslint-config-airbnb-typescript\": \"^17.1.0\",\n \"eslint-config-prettier\": \"^9.0.0\",\n \"eslint-plugin-import\": \"^2.28.1\",\n \"eslint-plugin-jsx-a11y\": \"^6.7.1\",\n \"eslint-plugin-prettier\": \"^5.0.1\",\n \"eslint-plugin-react\": \"^7.33.2\",\n \"eslint-plugin-react-hooks\": \"^4.6.0\",\n \"prettier\": \"^3.0.3\",\n \"react\": \"^18.2.0\",\n \"react-dom\": \"^18.2.0\",\n \"svelte\": \"^4.0.0\",\n \"tsup\": \"^8.5.0\",\n \"typescript\": \"^5.9.3\",\n \"vue\": \"^3.3.0\"\n }\n}\n","import type { HapticPreset } from \"./types\";\n\nexport const defaultPatterns = {\n success: {\n pattern: [{ duration: 50 }, { delay: 50, duration: 50 }],\n description: \"A series of taps indicating success.\",\n },\n nudge: {\n pattern: [\n { duration: 80, intensity: 0.8 },\n { delay: 80, duration: 50, intensity: 0.3 },\n ],\n description: \"A series of taps indicating a nudge.\",\n },\n error: {\n pattern: [\n { duration: 50, intensity: 0.75 },\n { delay: 50, duration: 50, intensity: 0.75 },\n { delay: 50, duration: 50, intensity: 0.75 },\n ],\n description: \"A series of taps indicating a warning or error.\",\n },\n buzz: {\n pattern: [{ duration: 1000, intensity: 1 }],\n description: \"A long vibration.\",\n },\n} as const satisfies Record<string, HapticPreset>;\n","import { defaultPatterns } from \"./patterns\";\nimport type {\n HapticInput,\n TriggerOptions,\n Vibration,\n WebHapticsOptions,\n} from \"./types\";\n\nconst TOGGLE_MIN = 16; // ms at intensity 1 (every frame)\nconst TOGGLE_MAX = 184; // range above min (0.5 intensity ≈ 100ms)\nconst MAX_PHASE_MS = 1000; // browser haptic window limit\nconst PWM_CYCLE = 20; // ms per intensity modulation cycle\n\n/** Convert any HapticInput into a Vibration array. */\nfunction normalizeInput(input: HapticInput): {\n vibrations: Vibration[];\n} | null {\n if (typeof input === \"number\") {\n return { vibrations: [{ duration: input }] };\n }\n\n if (typeof input === \"string\") {\n const preset = defaultPatterns[input as keyof typeof defaultPatterns];\n if (!preset) {\n console.warn(`[web-haptics] Unknown preset: \"${input}\"`);\n return null;\n }\n return { vibrations: preset.pattern.map((v) => ({ ...v })) };\n }\n\n if (Array.isArray(input)) {\n if (input.length === 0) return { vibrations: [] };\n\n // number[] shorthand — alternating on/off\n if (typeof input[0] === \"number\") {\n const nums = input as number[];\n const vibrations: Vibration[] = [];\n for (let i = 0; i < nums.length; i += 2) {\n const delay = i > 0 ? nums[i - 1]! : 0;\n vibrations.push({\n ...(delay > 0 && { delay }),\n duration: nums[i]!,\n });\n }\n return { vibrations };\n }\n\n // Vibration[]\n return { vibrations: (input as Vibration[]).map((v) => ({ ...v })) };\n }\n\n // HapticPreset\n return { vibrations: input.pattern.map((v) => ({ ...v })) };\n}\n\n/**\n * Apply PWM modulation to a single vibration duration at a given intensity.\n * Returns the flat on/off segments for this vibration.\n */\nfunction modulateVibration(duration: number, intensity: number): number[] {\n if (intensity >= 1) return [duration];\n if (intensity <= 0) return [];\n\n const onTime = Math.max(1, Math.round(PWM_CYCLE * intensity));\n const offTime = PWM_CYCLE - onTime;\n const result: number[] = [];\n\n let remaining = duration;\n while (remaining >= PWM_CYCLE) {\n result.push(onTime);\n result.push(offTime);\n remaining -= PWM_CYCLE;\n }\n if (remaining > 0) {\n const remOn = Math.max(1, Math.round(remaining * intensity));\n result.push(remOn);\n const remOff = remaining - remOn;\n if (remOff > 0) result.push(remOff);\n }\n\n return result;\n}\n\n/**\n * Convert Vibration[] to the flat number[] pattern for navigator.vibrate(),\n * applying per-vibration PWM intensity modulation.\n */\nfunction toVibratePattern(\n vibrations: Vibration[],\n defaultIntensity: number,\n): number[] {\n const result: number[] = [];\n\n for (let i = 0; i < vibrations.length; i++) {\n const vib = vibrations[i]!;\n const intensity = Math.max(\n 0,\n Math.min(1, vib.intensity ?? defaultIntensity),\n );\n const delay = vib.delay ?? 0;\n\n // Prepend delay: merge into trailing off-time or add new gap\n if (delay > 0) {\n if (result.length > 0 && result.length % 2 === 0) {\n result[result.length - 1]! += delay;\n } else {\n if (result.length === 0) result.push(0);\n result.push(delay);\n }\n }\n\n const modulated = modulateVibration(vib.duration, intensity);\n\n if (modulated.length === 0) {\n // Zero intensity — treat vibration as silence\n if (result.length > 0 && result.length % 2 === 0) {\n result[result.length - 1]! += vib.duration;\n } else if (vib.duration > 0) {\n result.push(0);\n result.push(vib.duration);\n }\n continue;\n }\n\n // Append modulated vibration segments\n for (const seg of modulated) {\n result.push(seg);\n }\n }\n\n return result;\n}\n\nlet instanceCounter = 0;\n\nexport class WebHaptics {\n private hapticLabel: HTMLLabelElement | null = null;\n private domInitialized = false;\n private instanceId: number;\n private debug: boolean;\n private showSwitch: boolean;\n private rafId: number | null = null;\n private patternResolve: (() => void) | null = null;\n private audioCtx: AudioContext | null = null;\n private audioFilter: BiquadFilterNode | null = null;\n private audioGain: GainNode | null = null;\n private audioBuffer: AudioBuffer | null = null;\n\n constructor(options?: WebHapticsOptions) {\n this.instanceId = ++instanceCounter;\n this.debug = options?.debug ?? false;\n this.showSwitch = options?.showSwitch ?? false;\n }\n\n static readonly isSupported: boolean =\n typeof navigator !== \"undefined\" &&\n typeof navigator.vibrate === \"function\";\n\n async trigger(\n input: HapticInput = [{ duration: 10 }],\n options?: TriggerOptions,\n ): Promise<void> {\n const normalized = normalizeInput(input);\n if (!normalized) return;\n\n const { vibrations } = normalized;\n if (vibrations.length === 0) return;\n\n const defaultIntensity = Math.max(\n 0,\n Math.min(1, options?.intensity ?? 0.5),\n );\n\n // Validate and clamp durations\n for (const vib of vibrations) {\n if (vib.duration > MAX_PHASE_MS) vib.duration = MAX_PHASE_MS;\n if (\n !Number.isFinite(vib.duration) ||\n vib.duration < 0 ||\n (vib.delay !== undefined &&\n (!Number.isFinite(vib.delay) || vib.delay < 0))\n ) {\n console.warn(\n `[web-haptics] Invalid vibration values. Durations and delays must be finite non-negative numbers.`,\n );\n return;\n }\n }\n\n if (WebHaptics.isSupported) {\n navigator.vibrate(toVibratePattern(vibrations, defaultIntensity));\n }\n\n if (!WebHaptics.isSupported || this.debug) {\n this.ensureDOM();\n if (!this.hapticLabel) return;\n\n if (this.debug) {\n await this.ensureAudio();\n }\n\n this.stopPattern();\n\n // Fire first click synchronously to stay within user gesture context\n this.hapticLabel.click();\n if (this.debug && this.audioCtx) {\n const firstIntensity = Math.max(\n 0,\n Math.min(1, vibrations[0]!.intensity ?? defaultIntensity),\n );\n this.playClick(firstIntensity);\n }\n\n await this.runPattern(vibrations, defaultIntensity);\n }\n }\n\n cancel(): void {\n this.stopPattern();\n if (WebHaptics.isSupported) {\n navigator.vibrate(0);\n }\n }\n\n destroy(): void {\n this.stopPattern();\n if (this.hapticLabel) {\n this.hapticLabel.remove();\n this.hapticLabel = null;\n this.domInitialized = false;\n }\n if (this.audioCtx) {\n this.audioCtx.close();\n this.audioCtx = null;\n this.audioFilter = null;\n this.audioGain = null;\n this.audioBuffer = null;\n }\n }\n\n setDebug(debug: boolean): void {\n this.debug = debug;\n if (!debug && this.audioCtx) {\n this.audioCtx.close();\n this.audioCtx = null;\n this.audioFilter = null;\n this.audioGain = null;\n this.audioBuffer = null;\n }\n }\n\n setShowSwitch(show: boolean): void {\n this.showSwitch = show;\n if (this.hapticLabel) {\n const checkbox = this.hapticLabel.querySelector(\"input\");\n this.hapticLabel.style.display = show ? \"\" : \"none\";\n if (checkbox) checkbox.style.display = show ? \"\" : \"none\";\n }\n }\n\n private stopPattern(): void {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n this.patternResolve?.();\n this.patternResolve = null;\n }\n\n private runPattern(\n vibrations: Vibration[],\n defaultIntensity: number,\n ): Promise<void> {\n return new Promise((resolve) => {\n this.patternResolve = resolve;\n\n // Build phase boundaries: each vibration has an optional delay then an \"on\" phase\n const phases: { end: number; isOn: boolean; intensity: number }[] = [];\n let cumulative = 0;\n for (const vib of vibrations) {\n const intensity = Math.max(\n 0,\n Math.min(1, vib.intensity ?? defaultIntensity),\n );\n const delay = vib.delay ?? 0;\n if (delay > 0) {\n cumulative += delay;\n phases.push({ end: cumulative, isOn: false, intensity: 0 });\n }\n cumulative += vib.duration;\n phases.push({ end: cumulative, isOn: true, intensity });\n }\n const totalDuration = cumulative;\n\n let startTime = 0;\n let lastToggleTime = -1;\n\n const loop = (time: number) => {\n if (startTime === 0) startTime = time;\n const elapsed = time - startTime;\n\n if (elapsed >= totalDuration) {\n this.rafId = null;\n this.patternResolve = null;\n resolve();\n return;\n }\n\n // Find current phase\n let phase = phases[0]!;\n for (const p of phases) {\n if (elapsed < p.end) {\n phase = p;\n break;\n }\n }\n\n if (phase.isOn) {\n const toggleInterval =\n TOGGLE_MIN + (1 - phase.intensity) * TOGGLE_MAX;\n\n if (lastToggleTime === -1) {\n lastToggleTime = time;\n } else if (time - lastToggleTime >= toggleInterval) {\n this.hapticLabel?.click();\n if (this.debug && this.audioCtx) {\n this.playClick(phase.intensity);\n }\n lastToggleTime = time;\n }\n }\n\n this.rafId = requestAnimationFrame(loop);\n };\n this.rafId = requestAnimationFrame(loop);\n });\n }\n\n private playClick(intensity: number): void {\n if (\n !this.audioCtx ||\n !this.audioFilter ||\n !this.audioGain ||\n !this.audioBuffer\n )\n return;\n\n const data = this.audioBuffer.getChannelData(0);\n for (let i = 0; i < data.length; i++) {\n data[i] = (Math.random() * 2 - 1) * Math.exp(-i / 25);\n }\n\n this.audioGain.gain.value = 0.5 * intensity;\n\n const source = this.audioCtx.createBufferSource();\n source.buffer = this.audioBuffer;\n source.connect(this.audioFilter);\n source.onended = () => source.disconnect();\n source.start();\n }\n\n private async ensureAudio(): Promise<void> {\n if (!this.audioCtx && typeof AudioContext !== \"undefined\") {\n this.audioCtx = new AudioContext();\n\n this.audioFilter = this.audioCtx.createBiquadFilter();\n this.audioFilter.type = \"bandpass\";\n this.audioFilter.frequency.value = 4000;\n this.audioFilter.Q.value = 8;\n\n this.audioGain = this.audioCtx.createGain();\n this.audioFilter.connect(this.audioGain);\n this.audioGain.connect(this.audioCtx.destination);\n\n const duration = 0.004;\n this.audioBuffer = this.audioCtx.createBuffer(\n 1,\n this.audioCtx.sampleRate * duration,\n this.audioCtx.sampleRate,\n );\n const data = this.audioBuffer.getChannelData(0);\n for (let i = 0; i < data.length; i++) {\n data[i] = (Math.random() * 2 - 1) * Math.exp(-i / 25);\n }\n }\n if (this.audioCtx?.state === \"suspended\") {\n await this.audioCtx.resume();\n }\n }\n\n private ensureDOM(): void {\n if (this.domInitialized) return;\n if (typeof document === \"undefined\") return;\n\n const id = `web-haptics-${this.instanceId}`;\n\n const hapticLabel = document.createElement(\"label\");\n hapticLabel.setAttribute(\"for\", id);\n hapticLabel.textContent = \"Haptic feedback\";\n hapticLabel.style.position = \"fixed\";\n hapticLabel.style.bottom = \"10px\";\n hapticLabel.style.left = \"10px\";\n hapticLabel.style.padding = \"5px 10px\";\n hapticLabel.style.backgroundColor = \"rgba(0, 0, 0, 0.7)\";\n hapticLabel.style.color = \"white\";\n hapticLabel.style.fontFamily = \"sans-serif\";\n hapticLabel.style.fontSize = \"14px\";\n hapticLabel.style.borderRadius = \"4px\";\n hapticLabel.style.zIndex = \"9999\";\n hapticLabel.style.userSelect = \"none\";\n this.hapticLabel = hapticLabel;\n\n const hapticCheckbox = document.createElement(\"input\");\n hapticCheckbox.type = \"checkbox\";\n hapticCheckbox.setAttribute(\"switch\", \"\");\n hapticCheckbox.id = id;\n hapticCheckbox.style.all = \"initial\";\n hapticCheckbox.style.appearance = \"auto\";\n\n if (!this.showSwitch) {\n hapticLabel.style.display = \"none\";\n hapticCheckbox.style.display = \"none\";\n }\n\n hapticLabel.appendChild(hapticCheckbox);\n document.body.appendChild(hapticLabel);\n this.domInitialized = true;\n }\n}\n"],"mappings":"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","names":["src_exports","__export","WebHaptics","defaultPatterns","version","__toCommonJS","version","defaultPatterns","TOGGLE_MIN","TOGGLE_MAX","MAX_PHASE_MS","PWM_CYCLE","normalizeInput","input","preset","defaultPatterns","v","nums","vibrations","delay","modulateVibration","duration","intensity","onTime","offTime","result","remaining","remOn","remOff","toVibratePattern","defaultIntensity","vib","modulated","seg","instanceCounter","WebHaptics","_WebHaptics","options","normalized","firstIntensity","debug","show","checkbox","resolve","phases","cumulative","totalDuration","startTime","lastToggleTime","loop","time","elapsed","phase","p","toggleInterval","data","i","source","id","hapticLabel","hapticCheckbox"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import{a as e,b as t}from"./chunk-
|
|
2
|
+
import{a as e,b as t}from"./chunk-42RGEEVN.mjs";var s="0.0.3";export{t as WebHaptics,e as defaultPatterns,s as version};
|
|
3
3
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../package.json"],"sourcesContent":["{\n \"name\": \"web-haptics\",\n \"version\": \"0.0.
|
|
1
|
+
{"version":3,"sources":["../package.json"],"sourcesContent":["{\n \"name\": \"web-haptics\",\n \"version\": \"0.0.3\",\n \"description\": \"Haptic feedback for the mobile web.\",\n \"homepage\": \"https://haptics.lochie.me\",\n \"sideEffects\": false,\n \"author\": \"Lochie Axon\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/lochie/web-haptics.git\",\n \"directory\": \"packages/web-haptics\"\n },\n \"types\": \"dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.mjs\",\n \"require\": \"./dist/index.js\"\n },\n \"./react\": {\n \"types\": \"./dist/react/index.d.ts\",\n \"import\": \"./dist/react/index.mjs\",\n \"require\": \"./dist/react/index.js\"\n },\n \"./vue\": {\n \"types\": \"./dist/vue/index.d.ts\",\n \"import\": \"./dist/vue/index.mjs\",\n \"require\": \"./dist/vue/index.js\"\n },\n \"./svelte\": {\n \"types\": \"./dist/svelte/index.d.ts\",\n \"import\": \"./dist/svelte/index.mjs\",\n \"require\": \"./dist/svelte/index.js\"\n }\n },\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"lint\": \"eslint -c .eslintrc.cjs ./src/**/*.{ts,tsx}\",\n \"lint:fix\": \"eslint --fix -c .eslintrc.cjs ./src/**/*.{ts,tsx}\",\n \"pre-commit\": \"lint-staged\",\n \"prepublishOnly\": \"pnpm build\"\n },\n \"keywords\": [\n \"haptics\",\n \"vibration\",\n \"feedback\",\n \"web\",\n \"mobile\",\n \"react\",\n \"vue\",\n \"svelte\",\n \"typescript\"\n ],\n \"bugs\": {\n \"url\": \"https://github.com/lochie/web-haptics/issues\"\n },\n \"files\": [\n \"dist/*\",\n \"LICENSE\",\n \"README.md\"\n ],\n \"peerDependencies\": {\n \"react\": \">=18\",\n \"react-dom\": \">=18\",\n \"vue\": \">=3\",\n \"svelte\": \">=4\"\n },\n \"peerDependenciesMeta\": {\n \"react\": {\n \"optional\": true\n },\n \"react-dom\": {\n \"optional\": true\n },\n \"vue\": {\n \"optional\": true\n },\n \"svelte\": {\n \"optional\": true\n }\n },\n \"devDependencies\": {\n \"@types/react\": \"^18.2.15\",\n \"@typescript-eslint/eslint-plugin\": \"^6.8.0\",\n \"@typescript-eslint/parser\": \"^6.8.0\",\n \"eslint\": \"^9.36.0\",\n \"eslint-config-airbnb\": \"^19.0.4\",\n \"eslint-config-airbnb-typescript\": \"^17.1.0\",\n \"eslint-config-prettier\": \"^9.0.0\",\n \"eslint-plugin-import\": \"^2.28.1\",\n \"eslint-plugin-jsx-a11y\": \"^6.7.1\",\n \"eslint-plugin-prettier\": \"^5.0.1\",\n \"eslint-plugin-react\": \"^7.33.2\",\n \"eslint-plugin-react-hooks\": \"^4.6.0\",\n \"prettier\": \"^3.0.3\",\n \"react\": \"^18.2.0\",\n \"react-dom\": \"^18.2.0\",\n \"svelte\": \"^4.0.0\",\n \"tsup\": \"^8.5.0\",\n \"typescript\": \"^5.9.3\",\n \"vue\": \"^3.3.0\"\n }\n}\n"],"mappings":";gDAEE,IAAAA,EAAW","names":["version"]}
|
package/dist/react/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { W as WebHapticsOptions, H as HapticInput, T as TriggerOptions } from '../types-
|
|
1
|
+
import { W as WebHapticsOptions, H as HapticInput, T as TriggerOptions } from '../types-DkzcH7AA.mjs';
|
|
2
2
|
|
|
3
3
|
declare function useWebHaptics(options?: WebHapticsOptions): {
|
|
4
4
|
trigger: (input?: HapticInput, options?: TriggerOptions) => Promise<void> | undefined;
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { W as WebHapticsOptions, H as HapticInput, T as TriggerOptions } from '../types-
|
|
1
|
+
import { W as WebHapticsOptions, H as HapticInput, T as TriggerOptions } from '../types-DkzcH7AA.js';
|
|
2
2
|
|
|
3
3
|
declare function useWebHaptics(options?: WebHapticsOptions): {
|
|
4
4
|
trigger: (input?: HapticInput, options?: TriggerOptions) => Promise<void> | undefined;
|
package/dist/react/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
"use strict";var
|
|
2
|
+
"use strict";var m=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var w=Object.getOwnPropertyNames;var I=Object.prototype.hasOwnProperty;var M=(a,i)=>{for(var t in i)m(a,t,{get:i[t],enumerable:!0})},S=(a,i,t,e)=>{if(i&&typeof i=="object"||typeof i=="function")for(let n of w(i))!I.call(a,n)&&n!==t&&m(a,n,{get:()=>i[n],enumerable:!(e=C(i,n))||e.enumerable});return a};var A=a=>S(m({},"__esModule",{value:!0}),a);var G={};M(G,{useWebHaptics:()=>x});module.exports=A(G);var l=require("react");var g={success:{pattern:[{duration:50},{delay:50,duration:50}],description:"A series of taps indicating success."},nudge:{pattern:[{duration:80,intensity:.8},{delay:80,duration:50,intensity:.3}],description:"A series of taps indicating a nudge."},error:{pattern:[{duration:50,intensity:.75},{delay:50,duration:50,intensity:.75},{delay:50,duration:50,intensity:.75}],description:"A series of taps indicating a warning or error."},buzz:{pattern:[{duration:1e3,intensity:1}],description:"A long vibration."}};var F=16,O=184,v=1e3,b=20;function P(a){if(typeof a=="number")return{vibrations:[{duration:a}]};if(typeof a=="string"){let i=g[a];return i?{vibrations:i.pattern.map(t=>({...t}))}:(console.warn(`[web-haptics] Unknown preset: "${a}"`),null)}if(Array.isArray(a)){if(a.length===0)return{vibrations:[]};if(typeof a[0]=="number"){let i=a,t=[];for(let e=0;e<i.length;e+=2){let n=e>0?i[e-1]:0;t.push({...n>0&&{delay:n},duration:i[e]})}return{vibrations:t}}return{vibrations:a.map(i=>({...i}))}}return{vibrations:a.pattern.map(i=>({...i}))}}function H(a,i){if(i>=1)return[a];if(i<=0)return[];let t=Math.max(1,Math.round(b*i)),e=b-t,n=[],r=a;for(;r>=b;)n.push(t),n.push(e),r-=b;if(r>0){let s=Math.max(1,Math.round(r*i));n.push(s);let o=r-s;o>0&&n.push(o)}return n}function L(a,i){let t=[];for(let e=0;e<a.length;e++){let n=a[e],r=Math.max(0,Math.min(1,n.intensity??i)),s=n.delay??0;s>0&&(t.length>0&&t.length%2===0?t[t.length-1]+=s:(t.length===0&&t.push(0),t.push(s)));let o=H(n.duration,r);if(o.length===0){t.length>0&&t.length%2===0?t[t.length-1]+=n.duration:n.duration>0&&(t.push(0),t.push(n.duration));continue}for(let c of o)t.push(c)}return t}var k=0,h=class a{hapticLabel=null;domInitialized=!1;instanceId;debug;showSwitch;rafId=null;patternResolve=null;audioCtx=null;audioFilter=null;audioGain=null;audioBuffer=null;constructor(i){this.instanceId=++k,this.debug=i?.debug??!1,this.showSwitch=i?.showSwitch??!1}static isSupported=typeof navigator<"u"&&typeof navigator.vibrate=="function";async trigger(i=[{duration:10}],t){let e=P(i);if(!e)return;let{vibrations:n}=e;if(n.length===0)return;let r=Math.max(0,Math.min(1,t?.intensity??.5));for(let s of n)if(s.duration>v&&(s.duration=v),!Number.isFinite(s.duration)||s.duration<0||s.delay!==void 0&&(!Number.isFinite(s.delay)||s.delay<0)){console.warn("[web-haptics] Invalid vibration values. Durations and delays must be finite non-negative numbers.");return}if(a.isSupported&&navigator.vibrate(L(n,r)),!a.isSupported||this.debug){if(this.ensureDOM(),!this.hapticLabel)return;if(this.debug&&await this.ensureAudio(),this.stopPattern(),this.hapticLabel.click(),this.debug&&this.audioCtx){let s=Math.max(0,Math.min(1,n[0].intensity??r));this.playClick(s)}await this.runPattern(n,r)}}cancel(){this.stopPattern(),a.isSupported&&navigator.vibrate(0)}destroy(){this.stopPattern(),this.hapticLabel&&(this.hapticLabel.remove(),this.hapticLabel=null,this.domInitialized=!1),this.audioCtx&&(this.audioCtx.close(),this.audioCtx=null,this.audioFilter=null,this.audioGain=null,this.audioBuffer=null)}setDebug(i){this.debug=i,!i&&this.audioCtx&&(this.audioCtx.close(),this.audioCtx=null,this.audioFilter=null,this.audioGain=null,this.audioBuffer=null)}setShowSwitch(i){if(this.showSwitch=i,this.hapticLabel){let t=this.hapticLabel.querySelector("input");this.hapticLabel.style.display=i?"":"none",t&&(t.style.display=i?"":"none")}}stopPattern(){this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.patternResolve?.(),this.patternResolve=null}runPattern(i,t){return new Promise(e=>{this.patternResolve=e;let n=[],r=0;for(let u of i){let p=Math.max(0,Math.min(1,u.intensity??t)),d=u.delay??0;d>0&&(r+=d,n.push({end:r,isOn:!1,intensity:0})),r+=u.duration,n.push({end:r,isOn:!0,intensity:p})}let s=r,o=0,c=-1,y=u=>{o===0&&(o=u);let p=u-o;if(p>=s){this.rafId=null,this.patternResolve=null,e();return}let d=n[0];for(let f of n)if(p<f.end){d=f;break}if(d.isOn){let f=F+(1-d.intensity)*O;c===-1?c=u:u-c>=f&&(this.hapticLabel?.click(),this.debug&&this.audioCtx&&this.playClick(d.intensity),c=u)}this.rafId=requestAnimationFrame(y)};this.rafId=requestAnimationFrame(y)})}playClick(i){if(!this.audioCtx||!this.audioFilter||!this.audioGain||!this.audioBuffer)return;let t=this.audioBuffer.getChannelData(0);for(let n=0;n<t.length;n++)t[n]=(Math.random()*2-1)*Math.exp(-n/25);this.audioGain.gain.value=.5*i;let e=this.audioCtx.createBufferSource();e.buffer=this.audioBuffer,e.connect(this.audioFilter),e.onended=()=>e.disconnect(),e.start()}async ensureAudio(){if(!this.audioCtx&&typeof AudioContext<"u"){this.audioCtx=new AudioContext,this.audioFilter=this.audioCtx.createBiquadFilter(),this.audioFilter.type="bandpass",this.audioFilter.frequency.value=4e3,this.audioFilter.Q.value=8,this.audioGain=this.audioCtx.createGain(),this.audioFilter.connect(this.audioGain),this.audioGain.connect(this.audioCtx.destination);let i=.004;this.audioBuffer=this.audioCtx.createBuffer(1,this.audioCtx.sampleRate*i,this.audioCtx.sampleRate);let t=this.audioBuffer.getChannelData(0);for(let e=0;e<t.length;e++)t[e]=(Math.random()*2-1)*Math.exp(-e/25)}this.audioCtx?.state==="suspended"&&await this.audioCtx.resume()}ensureDOM(){if(this.domInitialized||typeof document>"u")return;let i=`web-haptics-${this.instanceId}`,t=document.createElement("label");t.setAttribute("for",i),t.textContent="Haptic feedback",t.style.position="fixed",t.style.bottom="10px",t.style.left="10px",t.style.padding="5px 10px",t.style.backgroundColor="rgba(0, 0, 0, 0.7)",t.style.color="white",t.style.fontFamily="sans-serif",t.style.fontSize="14px",t.style.borderRadius="4px",t.style.zIndex="9999",t.style.userSelect="none",this.hapticLabel=t;let e=document.createElement("input");e.type="checkbox",e.setAttribute("switch",""),e.id=i,e.style.all="initial",e.style.appearance="auto",this.showSwitch||(t.style.display="none",e.style.display="none"),t.appendChild(e),document.body.appendChild(t),this.domInitialized=!0}};function x(a){let i=(0,l.useRef)(null);(0,l.useEffect)(()=>(i.current=new h(a),()=>{i.current?.destroy(),i.current=null}),[]),(0,l.useEffect)(()=>{i.current?.setDebug(a?.debug??!1)},[a?.debug]),(0,l.useEffect)(()=>{i.current?.setShowSwitch(a?.showSwitch??!1)},[a?.showSwitch]);let t=(0,l.useCallback)((r,s)=>i.current?.trigger(r,s),[]),e=(0,l.useCallback)(()=>i.current?.cancel(),[]),n=h.isSupported;return{trigger:t,cancel:e,isSupported:n}}0&&(module.exports={useWebHaptics});
|
|
3
3
|
//# sourceMappingURL=index.js.map
|