@haptics/vue 1.0.0 → 1.0.1

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/dist/index.cjs CHANGED
@@ -22,8 +22,11 @@ function _setPrefersReducedMotion(value) {
22
22
 
23
23
  // src/plugin.ts
24
24
  var HAPTICS_INJECTION_KEY = /* @__PURE__ */ Symbol("haptics");
25
+ var _cleanup = null;
25
26
  var HapticsPlugin = {
26
27
  install(app, options = {}) {
28
+ _cleanup?.();
29
+ _cleanup = null;
27
30
  const patterns = {
28
31
  ...core.PRESETS,
29
32
  ...options.patterns
@@ -31,31 +34,45 @@ var HapticsPlugin = {
31
34
  const respectReducedMotion = options.respectReducedMotion ?? true;
32
35
  app.provide(HAPTICS_INJECTION_KEY, { patterns, respectReducedMotion });
33
36
  _setConfig(patterns, respectReducedMotion);
37
+ const cleanups = [];
38
+ let lastCancel = null;
34
39
  if (typeof document !== "undefined" && core.isIOS()) {
35
40
  let prefersReducedMotion = false;
36
41
  if (typeof window !== "undefined" && window.matchMedia) {
37
42
  const mql = window.matchMedia("(prefers-reduced-motion: reduce)");
38
43
  prefersReducedMotion = mql.matches;
39
44
  _setPrefersReducedMotion(prefersReducedMotion);
40
- mql.addEventListener("change", (e) => {
45
+ const onMqlChange = (e) => {
41
46
  prefersReducedMotion = e.matches;
42
47
  _setPrefersReducedMotion(e.matches);
43
- });
48
+ };
49
+ mql.addEventListener("change", onMqlChange);
50
+ cleanups.push(
51
+ () => mql.removeEventListener("change", onMqlChange)
52
+ );
44
53
  }
45
54
  const handler = (e) => {
46
55
  if (respectReducedMotion && prefersReducedMotion) return;
47
56
  const target = e.target?.closest("[data-haptic]");
48
57
  if (!target) return;
49
58
  const action = target.getAttribute("data-haptic");
50
- if (action && action in patterns) {
51
- core.schedulePattern(patterns[action]);
59
+ if (action && Object.prototype.hasOwnProperty.call(patterns, action)) {
60
+ lastCancel = core.schedulePattern(patterns[action]);
52
61
  }
53
62
  };
54
63
  document.addEventListener("click", handler, {
55
64
  capture: true,
56
65
  passive: true
57
66
  });
67
+ cleanups.push(
68
+ () => document.removeEventListener("click", handler, { capture: true })
69
+ );
58
70
  }
71
+ _cleanup = () => {
72
+ for (const fn of cleanups) fn();
73
+ lastCancel?.();
74
+ lastCancel = null;
75
+ };
59
76
  }
60
77
  };
61
78
  var vHaptic = {
@@ -64,9 +81,11 @@ var vHaptic = {
64
81
  if (!action) return;
65
82
  el.setAttribute("data-haptic", action);
66
83
  if (core.isVibrationSupported()) {
67
- const handler = () => {
84
+ const handler = (e) => {
85
+ if (e.defaultPrevented) return;
68
86
  if (_shouldSuppress()) return;
69
87
  const patterns = _getPatterns();
88
+ if (!Object.prototype.hasOwnProperty.call(patterns, action)) return;
70
89
  const pattern = patterns[action];
71
90
  if (pattern) {
72
91
  navigator.vibrate(core.toVibrateSequence(pattern));
@@ -112,6 +131,7 @@ function useHaptics() {
112
131
  });
113
132
  const trigger = (action) => {
114
133
  if (respectReducedMotion && prefersReducedMotion.value) return;
134
+ if (!Object.prototype.hasOwnProperty.call(patterns, action)) return;
115
135
  const pattern = patterns[action];
116
136
  if (!pattern) return;
117
137
  if (core.isVibrationSupported()) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/shared.ts","../src/plugin.ts","../src/directive.ts","../src/use-haptics.ts"],"names":["PRESETS","isIOS","schedulePattern","isVibrationSupported","toVibrateSequence","inject","ref","onMounted","onUnmounted"],"mappings":"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","file":"index.cjs","sourcesContent":["import { PRESETS } from \"@haptics/core\";\nimport type { HapticPattern } from \"@haptics/core\";\n\nlet _patterns: Record<string, HapticPattern> = { ...PRESETS };\nlet _respectReducedMotion = true;\nlet _prefersReducedMotion = false;\n\nexport function _setConfig(\n\tpatterns: Record<string, HapticPattern>,\n\trespectReducedMotion: boolean,\n): void {\n\t_patterns = patterns;\n\t_respectReducedMotion = respectReducedMotion;\n}\n\nexport function _getPatterns(): Record<string, HapticPattern> {\n\treturn _patterns;\n}\n\nexport function _shouldSuppress(): boolean {\n\treturn _respectReducedMotion && _prefersReducedMotion;\n}\n\nexport function _setPrefersReducedMotion(value: boolean): void {\n\t_prefersReducedMotion = value;\n}\n","import type { App, InjectionKey } from \"vue\";\nimport {\n\tPRESETS,\n\tisIOS,\n\tschedulePattern,\n} from \"@haptics/core\";\nimport type { HapticPattern } from \"@haptics/core\";\nimport { _setConfig, _setPrefersReducedMotion } from \"./shared\";\n\nexport interface HapticsPluginOptions {\n\t/** Custom patterns merged with built-in presets. Same-name customs override. */\n\tpatterns?: Record<string, HapticPattern>;\n\t/** Suppress haptics when prefers-reduced-motion is active. Default: true */\n\trespectReducedMotion?: boolean;\n}\n\nexport interface HapticsContext {\n\tpatterns: Record<string, HapticPattern>;\n\trespectReducedMotion: boolean;\n}\n\nexport const HAPTICS_INJECTION_KEY: InjectionKey<HapticsContext> =\n\tSymbol(\"haptics\");\n\nexport const HapticsPlugin = {\n\tinstall(app: App, options: HapticsPluginOptions = {}) {\n\t\tconst patterns: Record<string, HapticPattern> = {\n\t\t\t...PRESETS,\n\t\t\t...options.patterns,\n\t\t};\n\t\tconst respectReducedMotion = options.respectReducedMotion ?? true;\n\n\t\tapp.provide(HAPTICS_INJECTION_KEY, { patterns, respectReducedMotion });\n\t\t_setConfig(patterns, respectReducedMotion);\n\n\t\tif (typeof document !== \"undefined\" && isIOS()) {\n\t\t\tlet prefersReducedMotion = false;\n\n\t\t\tif (typeof window !== \"undefined\" && window.matchMedia) {\n\t\t\t\tconst mql = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n\t\t\t\tprefersReducedMotion = mql.matches;\n\t\t\t\t_setPrefersReducedMotion(prefersReducedMotion);\n\n\t\t\t\tmql.addEventListener(\"change\", (e) => {\n\t\t\t\t\tprefersReducedMotion = e.matches;\n\t\t\t\t\t_setPrefersReducedMotion(e.matches);\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst handler = (e: MouseEvent) => {\n\t\t\t\tif (respectReducedMotion && prefersReducedMotion) return;\n\t\t\t\tconst target = (e.target as Element)?.closest(\"[data-haptic]\");\n\t\t\t\tif (!target) return;\n\t\t\t\tconst action = target.getAttribute(\"data-haptic\");\n\t\t\t\tif (action && action in patterns) {\n\t\t\t\t\tschedulePattern(patterns[action]);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tdocument.addEventListener(\"click\", handler, {\n\t\t\t\tcapture: true,\n\t\t\t\tpassive: true,\n\t\t\t});\n\t\t}\n\t},\n};\n","import type { Directive } from \"vue\";\nimport { isVibrationSupported, toVibrateSequence } from \"@haptics/core\";\nimport { _getPatterns, _shouldSuppress } from \"./shared\";\n\ntype HapticValue = string;\n\n/**\n * v-haptic directive for declarative haptic feedback.\n *\n * Usage:\n * <button v-haptic=\"'success'\">Save</button>\n * <button v-haptic=\"'impact-heavy'\">Delete</button>\n *\n * On iOS: Sets data-haptic attribute so the plugin's capture-phase listener\n * handles it (preserving gesture chain).\n *\n * On Android: Registers a click handler that calls navigator.vibrate().\n */\nexport const vHaptic: Directive<HTMLElement, HapticValue> = {\n\tmounted(el, binding) {\n\t\tconst action = binding.value;\n\t\tif (!action) return;\n\n\t\tel.setAttribute(\"data-haptic\", action);\n\n\t\tif (isVibrationSupported()) {\n\t\t\tconst handler = () => {\n\t\t\t\tif (_shouldSuppress()) return;\n\t\t\t\tconst patterns = _getPatterns();\n\t\t\t\tconst pattern = patterns[action];\n\t\t\t\tif (pattern) {\n\t\t\t\t\tnavigator.vibrate(toVibrateSequence(pattern));\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tel.addEventListener(\"click\", handler);\n\t\t\t(el as any).__haptic_handler = handler;\n\t\t}\n\t},\n\n\tupdated(el, binding) {\n\t\tconst action = binding.value;\n\t\tif (action) {\n\t\t\tel.setAttribute(\"data-haptic\", action);\n\t\t} else {\n\t\t\tel.removeAttribute(\"data-haptic\");\n\t\t}\n\t},\n\n\tunmounted(el) {\n\t\tel.removeAttribute(\"data-haptic\");\n\t\tconst handler = (el as any).__haptic_handler;\n\t\tif (handler) {\n\t\t\tel.removeEventListener(\"click\", handler);\n\t\t\tdelete (el as any).__haptic_handler;\n\t\t}\n\t},\n};\n","import { inject, ref, onMounted, onUnmounted } from \"vue\";\nimport {\n\tPRESETS,\n\tisVibrationSupported,\n\tisIOS,\n\ttoVibrateSequence,\n\tschedulePattern,\n} from \"@haptics/core\";\nimport type { PresetName } from \"@haptics/core\";\nimport { HAPTICS_INJECTION_KEY } from \"./plugin\";\n\n/**\n * Composable for imperative haptic feedback.\n *\n * Works with or without HapticsPlugin — falls back to built-in presets.\n *\n * Returns plain values (not refs) for isSupported/isIOSSupported to match\n * the React hook's API shape. These don't change at runtime.\n */\nexport function useHaptics() {\n\tconst ctx = inject(HAPTICS_INJECTION_KEY, null);\n\tconst patterns = ctx?.patterns ?? PRESETS;\n\tconst respectReducedMotion = ctx?.respectReducedMotion ?? true;\n\n\tconst prefersReducedMotion = ref(false);\n\n\tonMounted(() => {\n\t\tif (typeof window === \"undefined\" || !window.matchMedia) return;\n\n\t\tconst mql = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n\t\tprefersReducedMotion.value = mql.matches;\n\n\t\tconst onChange = (e: MediaQueryListEvent) => {\n\t\t\tprefersReducedMotion.value = e.matches;\n\t\t};\n\t\tmql.addEventListener(\"change\", onChange);\n\n\t\tonUnmounted(() => {\n\t\t\tmql.removeEventListener(\"change\", onChange);\n\t\t});\n\t});\n\n\tconst trigger = (action: PresetName | (string & {})) => {\n\t\tif (respectReducedMotion && prefersReducedMotion.value) return;\n\n\t\tconst pattern = patterns[action as keyof typeof patterns];\n\t\tif (!pattern) return;\n\n\t\tif (isVibrationSupported()) {\n\t\t\tnavigator.vibrate(toVibrateSequence(pattern));\n\t\t} else if (isIOS()) {\n\t\t\tschedulePattern(pattern);\n\t\t}\n\t};\n\n\tconst cancel = () => {\n\t\tif (isVibrationSupported()) navigator.vibrate(0);\n\t};\n\n\treturn {\n\t\ttrigger,\n\t\tcancel,\n\t\tisSupported: isVibrationSupported(),\n\t\tisIOSSupported: isIOS(),\n\t};\n}\n"]}
1
+ {"version":3,"sources":["../src/shared.ts","../src/plugin.ts","../src/directive.ts","../src/use-haptics.ts"],"names":["PRESETS","isIOS","schedulePattern","isVibrationSupported","toVibrateSequence","inject","ref","onMounted","onUnmounted"],"mappings":"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","file":"index.cjs","sourcesContent":["import { PRESETS } from \"@haptics/core\";\nimport type { HapticPattern } from \"@haptics/core\";\n\nlet _patterns: Record<string, HapticPattern> = { ...PRESETS };\nlet _respectReducedMotion = true;\nlet _prefersReducedMotion = false;\n\nexport function _setConfig(\n\tpatterns: Record<string, HapticPattern>,\n\trespectReducedMotion: boolean,\n): void {\n\t_patterns = patterns;\n\t_respectReducedMotion = respectReducedMotion;\n}\n\nexport function _getPatterns(): Record<string, HapticPattern> {\n\treturn _patterns;\n}\n\nexport function _shouldSuppress(): boolean {\n\treturn _respectReducedMotion && _prefersReducedMotion;\n}\n\nexport function _setPrefersReducedMotion(value: boolean): void {\n\t_prefersReducedMotion = value;\n}\n","import type { App, InjectionKey } from \"vue\";\nimport {\n\tPRESETS,\n\tisIOS,\n\tschedulePattern,\n} from \"@haptics/core\";\nimport type { HapticPattern } from \"@haptics/core\";\nimport { _setConfig, _setPrefersReducedMotion } from \"./shared\";\n\nexport interface HapticsPluginOptions {\n\t/** Custom patterns merged with built-in presets. Same-name customs override. */\n\tpatterns?: Record<string, HapticPattern>;\n\t/** Suppress haptics when prefers-reduced-motion is active. Default: true */\n\trespectReducedMotion?: boolean;\n}\n\nexport interface HapticsContext {\n\tpatterns: Record<string, HapticPattern>;\n\trespectReducedMotion: boolean;\n}\n\nexport const HAPTICS_INJECTION_KEY: InjectionKey<HapticsContext> =\n\tSymbol(\"haptics\");\n\nlet _cleanup: (() => void) | null = null;\n\nexport const HapticsPlugin = {\n\tinstall(app: App, options: HapticsPluginOptions = {}) {\n\t\t// Idempotent: tear down listeners from a prior install before re-attaching.\n\t\t// Guards against HMR, repeated app.use() calls in tests, and multiple Vue apps.\n\t\t_cleanup?.();\n\t\t_cleanup = null;\n\n\t\tconst patterns: Record<string, HapticPattern> = {\n\t\t\t...PRESETS,\n\t\t\t...options.patterns,\n\t\t};\n\t\tconst respectReducedMotion = options.respectReducedMotion ?? true;\n\n\t\tapp.provide(HAPTICS_INJECTION_KEY, { patterns, respectReducedMotion });\n\t\t_setConfig(patterns, respectReducedMotion);\n\n\t\tconst cleanups: Array<() => void> = [];\n\t\tlet lastCancel: (() => void) | null = null;\n\n\t\tif (typeof document !== \"undefined\" && isIOS()) {\n\t\t\tlet prefersReducedMotion = false;\n\n\t\t\tif (typeof window !== \"undefined\" && window.matchMedia) {\n\t\t\t\tconst mql = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n\t\t\t\tprefersReducedMotion = mql.matches;\n\t\t\t\t_setPrefersReducedMotion(prefersReducedMotion);\n\n\t\t\t\tconst onMqlChange = (e: MediaQueryListEvent) => {\n\t\t\t\t\tprefersReducedMotion = e.matches;\n\t\t\t\t\t_setPrefersReducedMotion(e.matches);\n\t\t\t\t};\n\t\t\t\tmql.addEventListener(\"change\", onMqlChange);\n\t\t\t\tcleanups.push(() =>\n\t\t\t\t\tmql.removeEventListener(\"change\", onMqlChange),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst handler = (e: MouseEvent) => {\n\t\t\t\tif (respectReducedMotion && prefersReducedMotion) return;\n\t\t\t\tconst target = (e.target as Element)?.closest(\"[data-haptic]\");\n\t\t\t\tif (!target) return;\n\t\t\t\tconst action = target.getAttribute(\"data-haptic\");\n\t\t\t\tif (\n\t\t\t\t\taction &&\n\t\t\t\t\tObject.prototype.hasOwnProperty.call(patterns, action)\n\t\t\t\t) {\n\t\t\t\t\tlastCancel = schedulePattern(patterns[action]);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tdocument.addEventListener(\"click\", handler, {\n\t\t\t\tcapture: true,\n\t\t\t\tpassive: true,\n\t\t\t});\n\t\t\tcleanups.push(() =>\n\t\t\t\tdocument.removeEventListener(\"click\", handler, { capture: true }),\n\t\t\t);\n\t\t}\n\n\t\t_cleanup = () => {\n\t\t\tfor (const fn of cleanups) fn();\n\t\t\tlastCancel?.();\n\t\t\tlastCancel = null;\n\t\t};\n\t},\n};\n\n/** @internal Tear down listeners and reset config. For tests and HMR. */\nexport function _resetPlugin(): void {\n\t_cleanup?.();\n\t_cleanup = null;\n}\n","import type { Directive } from \"vue\";\nimport { isVibrationSupported, toVibrateSequence } from \"@haptics/core\";\nimport { _getPatterns, _shouldSuppress } from \"./shared\";\n\ntype HapticValue = string;\n\n/**\n * v-haptic directive for declarative haptic feedback.\n *\n * Usage:\n * <button v-haptic=\"'success'\">Save</button>\n * <button v-haptic=\"'impact-heavy'\">Delete</button>\n *\n * On iOS: Sets data-haptic attribute so the plugin's capture-phase listener\n * handles it (preserving gesture chain).\n *\n * On Android: Registers a click handler that calls navigator.vibrate().\n */\nexport const vHaptic: Directive<HTMLElement, HapticValue> = {\n\tmounted(el, binding) {\n\t\tconst action = binding.value;\n\t\tif (!action) return;\n\n\t\tel.setAttribute(\"data-haptic\", action);\n\n\t\tif (isVibrationSupported()) {\n\t\t\tconst handler = (e: Event) => {\n\t\t\t\tif (e.defaultPrevented) return;\n\t\t\t\tif (_shouldSuppress()) return;\n\t\t\t\tconst patterns = _getPatterns();\n\t\t\t\tif (!Object.prototype.hasOwnProperty.call(patterns, action)) return;\n\t\t\t\tconst pattern = patterns[action];\n\t\t\t\tif (pattern) {\n\t\t\t\t\tnavigator.vibrate(toVibrateSequence(pattern));\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tel.addEventListener(\"click\", handler);\n\t\t\t(el as any).__haptic_handler = handler;\n\t\t}\n\t},\n\n\tupdated(el, binding) {\n\t\tconst action = binding.value;\n\t\tif (action) {\n\t\t\tel.setAttribute(\"data-haptic\", action);\n\t\t} else {\n\t\t\tel.removeAttribute(\"data-haptic\");\n\t\t}\n\t},\n\n\tunmounted(el) {\n\t\tel.removeAttribute(\"data-haptic\");\n\t\tconst handler = (el as any).__haptic_handler;\n\t\tif (handler) {\n\t\t\tel.removeEventListener(\"click\", handler);\n\t\t\tdelete (el as any).__haptic_handler;\n\t\t}\n\t},\n};\n","import { inject, ref, onMounted, onUnmounted } from \"vue\";\nimport {\n\tPRESETS,\n\tisVibrationSupported,\n\tisIOS,\n\ttoVibrateSequence,\n\tschedulePattern,\n} from \"@haptics/core\";\nimport type { PresetName } from \"@haptics/core\";\nimport { HAPTICS_INJECTION_KEY } from \"./plugin\";\n\n/**\n * Composable for imperative haptic feedback.\n *\n * Works with or without HapticsPlugin — falls back to built-in presets.\n *\n * Returns plain values (not refs) for isSupported/isIOSSupported to match\n * the React hook's API shape. These don't change at runtime.\n */\nexport function useHaptics() {\n\tconst ctx = inject(HAPTICS_INJECTION_KEY, null);\n\tconst patterns = ctx?.patterns ?? PRESETS;\n\tconst respectReducedMotion = ctx?.respectReducedMotion ?? true;\n\n\tconst prefersReducedMotion = ref(false);\n\n\tonMounted(() => {\n\t\tif (typeof window === \"undefined\" || !window.matchMedia) return;\n\n\t\tconst mql = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n\t\tprefersReducedMotion.value = mql.matches;\n\n\t\tconst onChange = (e: MediaQueryListEvent) => {\n\t\t\tprefersReducedMotion.value = e.matches;\n\t\t};\n\t\tmql.addEventListener(\"change\", onChange);\n\n\t\tonUnmounted(() => {\n\t\t\tmql.removeEventListener(\"change\", onChange);\n\t\t});\n\t});\n\n\tconst trigger = (action: PresetName | (string & {})) => {\n\t\tif (respectReducedMotion && prefersReducedMotion.value) return;\n\n\t\tif (!Object.prototype.hasOwnProperty.call(patterns, action)) return;\n\t\tconst pattern = patterns[action as keyof typeof patterns];\n\t\tif (!pattern) return;\n\n\t\tif (isVibrationSupported()) {\n\t\t\tnavigator.vibrate(toVibrateSequence(pattern));\n\t\t} else if (isIOS()) {\n\t\t\tschedulePattern(pattern);\n\t\t}\n\t};\n\n\tconst cancel = () => {\n\t\tif (isVibrationSupported()) navigator.vibrate(0);\n\t};\n\n\treturn {\n\t\ttrigger,\n\t\tcancel,\n\t\tisSupported: isVibrationSupported(),\n\t\tisIOSSupported: isIOS(),\n\t};\n}\n"]}
package/dist/index.js CHANGED
@@ -20,8 +20,11 @@ function _setPrefersReducedMotion(value) {
20
20
 
21
21
  // src/plugin.ts
22
22
  var HAPTICS_INJECTION_KEY = /* @__PURE__ */ Symbol("haptics");
23
+ var _cleanup = null;
23
24
  var HapticsPlugin = {
24
25
  install(app, options = {}) {
26
+ _cleanup?.();
27
+ _cleanup = null;
25
28
  const patterns = {
26
29
  ...PRESETS,
27
30
  ...options.patterns
@@ -29,31 +32,45 @@ var HapticsPlugin = {
29
32
  const respectReducedMotion = options.respectReducedMotion ?? true;
30
33
  app.provide(HAPTICS_INJECTION_KEY, { patterns, respectReducedMotion });
31
34
  _setConfig(patterns, respectReducedMotion);
35
+ const cleanups = [];
36
+ let lastCancel = null;
32
37
  if (typeof document !== "undefined" && isIOS()) {
33
38
  let prefersReducedMotion = false;
34
39
  if (typeof window !== "undefined" && window.matchMedia) {
35
40
  const mql = window.matchMedia("(prefers-reduced-motion: reduce)");
36
41
  prefersReducedMotion = mql.matches;
37
42
  _setPrefersReducedMotion(prefersReducedMotion);
38
- mql.addEventListener("change", (e) => {
43
+ const onMqlChange = (e) => {
39
44
  prefersReducedMotion = e.matches;
40
45
  _setPrefersReducedMotion(e.matches);
41
- });
46
+ };
47
+ mql.addEventListener("change", onMqlChange);
48
+ cleanups.push(
49
+ () => mql.removeEventListener("change", onMqlChange)
50
+ );
42
51
  }
43
52
  const handler = (e) => {
44
53
  if (respectReducedMotion && prefersReducedMotion) return;
45
54
  const target = e.target?.closest("[data-haptic]");
46
55
  if (!target) return;
47
56
  const action = target.getAttribute("data-haptic");
48
- if (action && action in patterns) {
49
- schedulePattern(patterns[action]);
57
+ if (action && Object.prototype.hasOwnProperty.call(patterns, action)) {
58
+ lastCancel = schedulePattern(patterns[action]);
50
59
  }
51
60
  };
52
61
  document.addEventListener("click", handler, {
53
62
  capture: true,
54
63
  passive: true
55
64
  });
65
+ cleanups.push(
66
+ () => document.removeEventListener("click", handler, { capture: true })
67
+ );
56
68
  }
69
+ _cleanup = () => {
70
+ for (const fn of cleanups) fn();
71
+ lastCancel?.();
72
+ lastCancel = null;
73
+ };
57
74
  }
58
75
  };
59
76
  var vHaptic = {
@@ -62,9 +79,11 @@ var vHaptic = {
62
79
  if (!action) return;
63
80
  el.setAttribute("data-haptic", action);
64
81
  if (isVibrationSupported()) {
65
- const handler = () => {
82
+ const handler = (e) => {
83
+ if (e.defaultPrevented) return;
66
84
  if (_shouldSuppress()) return;
67
85
  const patterns = _getPatterns();
86
+ if (!Object.prototype.hasOwnProperty.call(patterns, action)) return;
68
87
  const pattern = patterns[action];
69
88
  if (pattern) {
70
89
  navigator.vibrate(toVibrateSequence(pattern));
@@ -110,6 +129,7 @@ function useHaptics() {
110
129
  });
111
130
  const trigger = (action) => {
112
131
  if (respectReducedMotion && prefersReducedMotion.value) return;
132
+ if (!Object.prototype.hasOwnProperty.call(patterns, action)) return;
113
133
  const pattern = patterns[action];
114
134
  if (!pattern) return;
115
135
  if (isVibrationSupported()) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/shared.ts","../src/plugin.ts","../src/directive.ts","../src/use-haptics.ts"],"names":["PRESETS","isVibrationSupported","toVibrateSequence","isIOS","schedulePattern"],"mappings":";;;AAGA,IAAI,SAAA,GAA2C,EAAE,GAAG,OAAA,EAAQ;AAC5D,IAAI,qBAAA,GAAwB,IAAA;AAC5B,IAAI,qBAAA,GAAwB,KAAA;AAErB,SAAS,UAAA,CACf,UACA,oBAAA,EACO;AACP,EAAA,SAAA,GAAY,QAAA;AACZ,EAAA,qBAAA,GAAwB,oBAAA;AACzB;AAEO,SAAS,YAAA,GAA8C;AAC7D,EAAA,OAAO,SAAA;AACR;AAEO,SAAS,eAAA,GAA2B;AAC1C,EAAA,OAAO,qBAAA,IAAyB,qBAAA;AACjC;AAEO,SAAS,yBAAyB,KAAA,EAAsB;AAC9D,EAAA,qBAAA,GAAwB,KAAA;AACzB;;;ACJO,IAAM,qBAAA,0BACL,SAAS,CAAA;AAEV,IAAM,aAAA,GAAgB;AAAA,EAC5B,OAAA,CAAQ,GAAA,EAAU,OAAA,GAAgC,EAAC,EAAG;AACrD,IAAA,MAAM,QAAA,GAA0C;AAAA,MAC/C,GAAGA,OAAAA;AAAA,MACH,GAAG,OAAA,CAAQ;AAAA,KACZ;AACA,IAAA,MAAM,oBAAA,GAAuB,QAAQ,oBAAA,IAAwB,IAAA;AAE7D,IAAA,GAAA,CAAI,OAAA,CAAQ,qBAAA,EAAuB,EAAE,QAAA,EAAU,sBAAsB,CAAA;AACrE,IAAA,UAAA,CAAW,UAAU,oBAAoB,CAAA;AAEzC,IAAA,IAAI,OAAO,QAAA,KAAa,WAAA,IAAe,KAAA,EAAM,EAAG;AAC/C,MAAA,IAAI,oBAAA,GAAuB,KAAA;AAE3B,MAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,MAAA,CAAO,UAAA,EAAY;AACvD,QAAA,MAAM,GAAA,GAAM,MAAA,CAAO,UAAA,CAAW,kCAAkC,CAAA;AAChE,QAAA,oBAAA,GAAuB,GAAA,CAAI,OAAA;AAC3B,QAAA,wBAAA,CAAyB,oBAAoB,CAAA;AAE7C,QAAA,GAAA,CAAI,gBAAA,CAAiB,QAAA,EAAU,CAAC,CAAA,KAAM;AACrC,UAAA,oBAAA,GAAuB,CAAA,CAAE,OAAA;AACzB,UAAA,wBAAA,CAAyB,EAAE,OAAO,CAAA;AAAA,QACnC,CAAC,CAAA;AAAA,MACF;AAEA,MAAA,MAAM,OAAA,GAAU,CAAC,CAAA,KAAkB;AAClC,QAAA,IAAI,wBAAwB,oBAAA,EAAsB;AAClD,QAAA,MAAM,MAAA,GAAU,CAAA,CAAE,MAAA,EAAoB,OAAA,CAAQ,eAAe,CAAA;AAC7D,QAAA,IAAI,CAAC,MAAA,EAAQ;AACb,QAAA,MAAM,MAAA,GAAS,MAAA,CAAO,YAAA,CAAa,aAAa,CAAA;AAChD,QAAA,IAAI,MAAA,IAAU,UAAU,QAAA,EAAU;AACjC,UAAA,eAAA,CAAgB,QAAA,CAAS,MAAM,CAAC,CAAA;AAAA,QACjC;AAAA,MACD,CAAA;AAEA,MAAA,QAAA,CAAS,gBAAA,CAAiB,SAAS,OAAA,EAAS;AAAA,QAC3C,OAAA,EAAS,IAAA;AAAA,QACT,OAAA,EAAS;AAAA,OACT,CAAA;AAAA,IACF;AAAA,EACD;AACD;AC/CO,IAAM,OAAA,GAA+C;AAAA,EAC3D,OAAA,CAAQ,IAAI,OAAA,EAAS;AACpB,IAAA,MAAM,SAAS,OAAA,CAAQ,KAAA;AACvB,IAAA,IAAI,CAAC,MAAA,EAAQ;AAEb,IAAA,EAAA,CAAG,YAAA,CAAa,eAAe,MAAM,CAAA;AAErC,IAAA,IAAI,sBAAqB,EAAG;AAC3B,MAAA,MAAM,UAAU,MAAM;AACrB,QAAA,IAAI,iBAAgB,EAAG;AACvB,QAAA,MAAM,WAAW,YAAA,EAAa;AAC9B,QAAA,MAAM,OAAA,GAAU,SAAS,MAAM,CAAA;AAC/B,QAAA,IAAI,OAAA,EAAS;AACZ,UAAA,SAAA,CAAU,OAAA,CAAQ,iBAAA,CAAkB,OAAO,CAAC,CAAA;AAAA,QAC7C;AAAA,MACD,CAAA;AAEA,MAAA,EAAA,CAAG,gBAAA,CAAiB,SAAS,OAAO,CAAA;AACpC,MAAC,GAAW,gBAAA,GAAmB,OAAA;AAAA,IAChC;AAAA,EACD,CAAA;AAAA,EAEA,OAAA,CAAQ,IAAI,OAAA,EAAS;AACpB,IAAA,MAAM,SAAS,OAAA,CAAQ,KAAA;AACvB,IAAA,IAAI,MAAA,EAAQ;AACX,MAAA,EAAA,CAAG,YAAA,CAAa,eAAe,MAAM,CAAA;AAAA,IACtC,CAAA,MAAO;AACN,MAAA,EAAA,CAAG,gBAAgB,aAAa,CAAA;AAAA,IACjC;AAAA,EACD,CAAA;AAAA,EAEA,UAAU,EAAA,EAAI;AACb,IAAA,EAAA,CAAG,gBAAgB,aAAa,CAAA;AAChC,IAAA,MAAM,UAAW,EAAA,CAAW,gBAAA;AAC5B,IAAA,IAAI,OAAA,EAAS;AACZ,MAAA,EAAA,CAAG,mBAAA,CAAoB,SAAS,OAAO,CAAA;AACvC,MAAA,OAAQ,EAAA,CAAW,gBAAA;AAAA,IACpB;AAAA,EACD;AACD;ACtCO,SAAS,UAAA,GAAa;AAC5B,EAAA,MAAM,GAAA,GAAM,MAAA,CAAO,qBAAA,EAAuB,IAAI,CAAA;AAC9C,EAAA,MAAM,QAAA,GAAW,KAAK,QAAA,IAAYA,OAAAA;AAClC,EAAA,MAAM,oBAAA,GAAuB,KAAK,oBAAA,IAAwB,IAAA;AAE1D,EAAA,MAAM,oBAAA,GAAuB,IAAI,KAAK,CAAA;AAEtC,EAAA,SAAA,CAAU,MAAM;AACf,IAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,CAAC,OAAO,UAAA,EAAY;AAEzD,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,UAAA,CAAW,kCAAkC,CAAA;AAChE,IAAA,oBAAA,CAAqB,QAAQ,GAAA,CAAI,OAAA;AAEjC,IAAA,MAAM,QAAA,GAAW,CAAC,CAAA,KAA2B;AAC5C,MAAA,oBAAA,CAAqB,QAAQ,CAAA,CAAE,OAAA;AAAA,IAChC,CAAA;AACA,IAAA,GAAA,CAAI,gBAAA,CAAiB,UAAU,QAAQ,CAAA;AAEvC,IAAA,WAAA,CAAY,MAAM;AACjB,MAAA,GAAA,CAAI,mBAAA,CAAoB,UAAU,QAAQ,CAAA;AAAA,IAC3C,CAAC,CAAA;AAAA,EACF,CAAC,CAAA;AAED,EAAA,MAAM,OAAA,GAAU,CAAC,MAAA,KAAuC;AACvD,IAAA,IAAI,oBAAA,IAAwB,qBAAqB,KAAA,EAAO;AAExD,IAAA,MAAM,OAAA,GAAU,SAAS,MAA+B,CAAA;AACxD,IAAA,IAAI,CAAC,OAAA,EAAS;AAEd,IAAA,IAAIC,sBAAqB,EAAG;AAC3B,MAAA,SAAA,CAAU,OAAA,CAAQC,iBAAAA,CAAkB,OAAO,CAAC,CAAA;AAAA,IAC7C,CAAA,MAAA,IAAWC,OAAM,EAAG;AACnB,MAAAC,gBAAgB,OAAO,CAAA;AAAA,IACxB;AAAA,EACD,CAAA;AAEA,EAAA,MAAM,SAAS,MAAM;AACpB,IAAA,IAAIH,oBAAAA,EAAqB,EAAG,SAAA,CAAU,OAAA,CAAQ,CAAC,CAAA;AAAA,EAChD,CAAA;AAEA,EAAA,OAAO;AAAA,IACN,OAAA;AAAA,IACA,MAAA;AAAA,IACA,aAAaA,oBAAAA,EAAqB;AAAA,IAClC,gBAAgBE,KAAAA;AAAM,GACvB;AACD","file":"index.js","sourcesContent":["import { PRESETS } from \"@haptics/core\";\nimport type { HapticPattern } from \"@haptics/core\";\n\nlet _patterns: Record<string, HapticPattern> = { ...PRESETS };\nlet _respectReducedMotion = true;\nlet _prefersReducedMotion = false;\n\nexport function _setConfig(\n\tpatterns: Record<string, HapticPattern>,\n\trespectReducedMotion: boolean,\n): void {\n\t_patterns = patterns;\n\t_respectReducedMotion = respectReducedMotion;\n}\n\nexport function _getPatterns(): Record<string, HapticPattern> {\n\treturn _patterns;\n}\n\nexport function _shouldSuppress(): boolean {\n\treturn _respectReducedMotion && _prefersReducedMotion;\n}\n\nexport function _setPrefersReducedMotion(value: boolean): void {\n\t_prefersReducedMotion = value;\n}\n","import type { App, InjectionKey } from \"vue\";\nimport {\n\tPRESETS,\n\tisIOS,\n\tschedulePattern,\n} from \"@haptics/core\";\nimport type { HapticPattern } from \"@haptics/core\";\nimport { _setConfig, _setPrefersReducedMotion } from \"./shared\";\n\nexport interface HapticsPluginOptions {\n\t/** Custom patterns merged with built-in presets. Same-name customs override. */\n\tpatterns?: Record<string, HapticPattern>;\n\t/** Suppress haptics when prefers-reduced-motion is active. Default: true */\n\trespectReducedMotion?: boolean;\n}\n\nexport interface HapticsContext {\n\tpatterns: Record<string, HapticPattern>;\n\trespectReducedMotion: boolean;\n}\n\nexport const HAPTICS_INJECTION_KEY: InjectionKey<HapticsContext> =\n\tSymbol(\"haptics\");\n\nexport const HapticsPlugin = {\n\tinstall(app: App, options: HapticsPluginOptions = {}) {\n\t\tconst patterns: Record<string, HapticPattern> = {\n\t\t\t...PRESETS,\n\t\t\t...options.patterns,\n\t\t};\n\t\tconst respectReducedMotion = options.respectReducedMotion ?? true;\n\n\t\tapp.provide(HAPTICS_INJECTION_KEY, { patterns, respectReducedMotion });\n\t\t_setConfig(patterns, respectReducedMotion);\n\n\t\tif (typeof document !== \"undefined\" && isIOS()) {\n\t\t\tlet prefersReducedMotion = false;\n\n\t\t\tif (typeof window !== \"undefined\" && window.matchMedia) {\n\t\t\t\tconst mql = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n\t\t\t\tprefersReducedMotion = mql.matches;\n\t\t\t\t_setPrefersReducedMotion(prefersReducedMotion);\n\n\t\t\t\tmql.addEventListener(\"change\", (e) => {\n\t\t\t\t\tprefersReducedMotion = e.matches;\n\t\t\t\t\t_setPrefersReducedMotion(e.matches);\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst handler = (e: MouseEvent) => {\n\t\t\t\tif (respectReducedMotion && prefersReducedMotion) return;\n\t\t\t\tconst target = (e.target as Element)?.closest(\"[data-haptic]\");\n\t\t\t\tif (!target) return;\n\t\t\t\tconst action = target.getAttribute(\"data-haptic\");\n\t\t\t\tif (action && action in patterns) {\n\t\t\t\t\tschedulePattern(patterns[action]);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tdocument.addEventListener(\"click\", handler, {\n\t\t\t\tcapture: true,\n\t\t\t\tpassive: true,\n\t\t\t});\n\t\t}\n\t},\n};\n","import type { Directive } from \"vue\";\nimport { isVibrationSupported, toVibrateSequence } from \"@haptics/core\";\nimport { _getPatterns, _shouldSuppress } from \"./shared\";\n\ntype HapticValue = string;\n\n/**\n * v-haptic directive for declarative haptic feedback.\n *\n * Usage:\n * <button v-haptic=\"'success'\">Save</button>\n * <button v-haptic=\"'impact-heavy'\">Delete</button>\n *\n * On iOS: Sets data-haptic attribute so the plugin's capture-phase listener\n * handles it (preserving gesture chain).\n *\n * On Android: Registers a click handler that calls navigator.vibrate().\n */\nexport const vHaptic: Directive<HTMLElement, HapticValue> = {\n\tmounted(el, binding) {\n\t\tconst action = binding.value;\n\t\tif (!action) return;\n\n\t\tel.setAttribute(\"data-haptic\", action);\n\n\t\tif (isVibrationSupported()) {\n\t\t\tconst handler = () => {\n\t\t\t\tif (_shouldSuppress()) return;\n\t\t\t\tconst patterns = _getPatterns();\n\t\t\t\tconst pattern = patterns[action];\n\t\t\t\tif (pattern) {\n\t\t\t\t\tnavigator.vibrate(toVibrateSequence(pattern));\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tel.addEventListener(\"click\", handler);\n\t\t\t(el as any).__haptic_handler = handler;\n\t\t}\n\t},\n\n\tupdated(el, binding) {\n\t\tconst action = binding.value;\n\t\tif (action) {\n\t\t\tel.setAttribute(\"data-haptic\", action);\n\t\t} else {\n\t\t\tel.removeAttribute(\"data-haptic\");\n\t\t}\n\t},\n\n\tunmounted(el) {\n\t\tel.removeAttribute(\"data-haptic\");\n\t\tconst handler = (el as any).__haptic_handler;\n\t\tif (handler) {\n\t\t\tel.removeEventListener(\"click\", handler);\n\t\t\tdelete (el as any).__haptic_handler;\n\t\t}\n\t},\n};\n","import { inject, ref, onMounted, onUnmounted } from \"vue\";\nimport {\n\tPRESETS,\n\tisVibrationSupported,\n\tisIOS,\n\ttoVibrateSequence,\n\tschedulePattern,\n} from \"@haptics/core\";\nimport type { PresetName } from \"@haptics/core\";\nimport { HAPTICS_INJECTION_KEY } from \"./plugin\";\n\n/**\n * Composable for imperative haptic feedback.\n *\n * Works with or without HapticsPlugin — falls back to built-in presets.\n *\n * Returns plain values (not refs) for isSupported/isIOSSupported to match\n * the React hook's API shape. These don't change at runtime.\n */\nexport function useHaptics() {\n\tconst ctx = inject(HAPTICS_INJECTION_KEY, null);\n\tconst patterns = ctx?.patterns ?? PRESETS;\n\tconst respectReducedMotion = ctx?.respectReducedMotion ?? true;\n\n\tconst prefersReducedMotion = ref(false);\n\n\tonMounted(() => {\n\t\tif (typeof window === \"undefined\" || !window.matchMedia) return;\n\n\t\tconst mql = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n\t\tprefersReducedMotion.value = mql.matches;\n\n\t\tconst onChange = (e: MediaQueryListEvent) => {\n\t\t\tprefersReducedMotion.value = e.matches;\n\t\t};\n\t\tmql.addEventListener(\"change\", onChange);\n\n\t\tonUnmounted(() => {\n\t\t\tmql.removeEventListener(\"change\", onChange);\n\t\t});\n\t});\n\n\tconst trigger = (action: PresetName | (string & {})) => {\n\t\tif (respectReducedMotion && prefersReducedMotion.value) return;\n\n\t\tconst pattern = patterns[action as keyof typeof patterns];\n\t\tif (!pattern) return;\n\n\t\tif (isVibrationSupported()) {\n\t\t\tnavigator.vibrate(toVibrateSequence(pattern));\n\t\t} else if (isIOS()) {\n\t\t\tschedulePattern(pattern);\n\t\t}\n\t};\n\n\tconst cancel = () => {\n\t\tif (isVibrationSupported()) navigator.vibrate(0);\n\t};\n\n\treturn {\n\t\ttrigger,\n\t\tcancel,\n\t\tisSupported: isVibrationSupported(),\n\t\tisIOSSupported: isIOS(),\n\t};\n}\n"]}
1
+ {"version":3,"sources":["../src/shared.ts","../src/plugin.ts","../src/directive.ts","../src/use-haptics.ts"],"names":["PRESETS","isVibrationSupported","toVibrateSequence","isIOS","schedulePattern"],"mappings":"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","file":"index.js","sourcesContent":["import { PRESETS } from \"@haptics/core\";\nimport type { HapticPattern } from \"@haptics/core\";\n\nlet _patterns: Record<string, HapticPattern> = { ...PRESETS };\nlet _respectReducedMotion = true;\nlet _prefersReducedMotion = false;\n\nexport function _setConfig(\n\tpatterns: Record<string, HapticPattern>,\n\trespectReducedMotion: boolean,\n): void {\n\t_patterns = patterns;\n\t_respectReducedMotion = respectReducedMotion;\n}\n\nexport function _getPatterns(): Record<string, HapticPattern> {\n\treturn _patterns;\n}\n\nexport function _shouldSuppress(): boolean {\n\treturn _respectReducedMotion && _prefersReducedMotion;\n}\n\nexport function _setPrefersReducedMotion(value: boolean): void {\n\t_prefersReducedMotion = value;\n}\n","import type { App, InjectionKey } from \"vue\";\nimport {\n\tPRESETS,\n\tisIOS,\n\tschedulePattern,\n} from \"@haptics/core\";\nimport type { HapticPattern } from \"@haptics/core\";\nimport { _setConfig, _setPrefersReducedMotion } from \"./shared\";\n\nexport interface HapticsPluginOptions {\n\t/** Custom patterns merged with built-in presets. Same-name customs override. */\n\tpatterns?: Record<string, HapticPattern>;\n\t/** Suppress haptics when prefers-reduced-motion is active. Default: true */\n\trespectReducedMotion?: boolean;\n}\n\nexport interface HapticsContext {\n\tpatterns: Record<string, HapticPattern>;\n\trespectReducedMotion: boolean;\n}\n\nexport const HAPTICS_INJECTION_KEY: InjectionKey<HapticsContext> =\n\tSymbol(\"haptics\");\n\nlet _cleanup: (() => void) | null = null;\n\nexport const HapticsPlugin = {\n\tinstall(app: App, options: HapticsPluginOptions = {}) {\n\t\t// Idempotent: tear down listeners from a prior install before re-attaching.\n\t\t// Guards against HMR, repeated app.use() calls in tests, and multiple Vue apps.\n\t\t_cleanup?.();\n\t\t_cleanup = null;\n\n\t\tconst patterns: Record<string, HapticPattern> = {\n\t\t\t...PRESETS,\n\t\t\t...options.patterns,\n\t\t};\n\t\tconst respectReducedMotion = options.respectReducedMotion ?? true;\n\n\t\tapp.provide(HAPTICS_INJECTION_KEY, { patterns, respectReducedMotion });\n\t\t_setConfig(patterns, respectReducedMotion);\n\n\t\tconst cleanups: Array<() => void> = [];\n\t\tlet lastCancel: (() => void) | null = null;\n\n\t\tif (typeof document !== \"undefined\" && isIOS()) {\n\t\t\tlet prefersReducedMotion = false;\n\n\t\t\tif (typeof window !== \"undefined\" && window.matchMedia) {\n\t\t\t\tconst mql = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n\t\t\t\tprefersReducedMotion = mql.matches;\n\t\t\t\t_setPrefersReducedMotion(prefersReducedMotion);\n\n\t\t\t\tconst onMqlChange = (e: MediaQueryListEvent) => {\n\t\t\t\t\tprefersReducedMotion = e.matches;\n\t\t\t\t\t_setPrefersReducedMotion(e.matches);\n\t\t\t\t};\n\t\t\t\tmql.addEventListener(\"change\", onMqlChange);\n\t\t\t\tcleanups.push(() =>\n\t\t\t\t\tmql.removeEventListener(\"change\", onMqlChange),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst handler = (e: MouseEvent) => {\n\t\t\t\tif (respectReducedMotion && prefersReducedMotion) return;\n\t\t\t\tconst target = (e.target as Element)?.closest(\"[data-haptic]\");\n\t\t\t\tif (!target) return;\n\t\t\t\tconst action = target.getAttribute(\"data-haptic\");\n\t\t\t\tif (\n\t\t\t\t\taction &&\n\t\t\t\t\tObject.prototype.hasOwnProperty.call(patterns, action)\n\t\t\t\t) {\n\t\t\t\t\tlastCancel = schedulePattern(patterns[action]);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tdocument.addEventListener(\"click\", handler, {\n\t\t\t\tcapture: true,\n\t\t\t\tpassive: true,\n\t\t\t});\n\t\t\tcleanups.push(() =>\n\t\t\t\tdocument.removeEventListener(\"click\", handler, { capture: true }),\n\t\t\t);\n\t\t}\n\n\t\t_cleanup = () => {\n\t\t\tfor (const fn of cleanups) fn();\n\t\t\tlastCancel?.();\n\t\t\tlastCancel = null;\n\t\t};\n\t},\n};\n\n/** @internal Tear down listeners and reset config. For tests and HMR. */\nexport function _resetPlugin(): void {\n\t_cleanup?.();\n\t_cleanup = null;\n}\n","import type { Directive } from \"vue\";\nimport { isVibrationSupported, toVibrateSequence } from \"@haptics/core\";\nimport { _getPatterns, _shouldSuppress } from \"./shared\";\n\ntype HapticValue = string;\n\n/**\n * v-haptic directive for declarative haptic feedback.\n *\n * Usage:\n * <button v-haptic=\"'success'\">Save</button>\n * <button v-haptic=\"'impact-heavy'\">Delete</button>\n *\n * On iOS: Sets data-haptic attribute so the plugin's capture-phase listener\n * handles it (preserving gesture chain).\n *\n * On Android: Registers a click handler that calls navigator.vibrate().\n */\nexport const vHaptic: Directive<HTMLElement, HapticValue> = {\n\tmounted(el, binding) {\n\t\tconst action = binding.value;\n\t\tif (!action) return;\n\n\t\tel.setAttribute(\"data-haptic\", action);\n\n\t\tif (isVibrationSupported()) {\n\t\t\tconst handler = (e: Event) => {\n\t\t\t\tif (e.defaultPrevented) return;\n\t\t\t\tif (_shouldSuppress()) return;\n\t\t\t\tconst patterns = _getPatterns();\n\t\t\t\tif (!Object.prototype.hasOwnProperty.call(patterns, action)) return;\n\t\t\t\tconst pattern = patterns[action];\n\t\t\t\tif (pattern) {\n\t\t\t\t\tnavigator.vibrate(toVibrateSequence(pattern));\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tel.addEventListener(\"click\", handler);\n\t\t\t(el as any).__haptic_handler = handler;\n\t\t}\n\t},\n\n\tupdated(el, binding) {\n\t\tconst action = binding.value;\n\t\tif (action) {\n\t\t\tel.setAttribute(\"data-haptic\", action);\n\t\t} else {\n\t\t\tel.removeAttribute(\"data-haptic\");\n\t\t}\n\t},\n\n\tunmounted(el) {\n\t\tel.removeAttribute(\"data-haptic\");\n\t\tconst handler = (el as any).__haptic_handler;\n\t\tif (handler) {\n\t\t\tel.removeEventListener(\"click\", handler);\n\t\t\tdelete (el as any).__haptic_handler;\n\t\t}\n\t},\n};\n","import { inject, ref, onMounted, onUnmounted } from \"vue\";\nimport {\n\tPRESETS,\n\tisVibrationSupported,\n\tisIOS,\n\ttoVibrateSequence,\n\tschedulePattern,\n} from \"@haptics/core\";\nimport type { PresetName } from \"@haptics/core\";\nimport { HAPTICS_INJECTION_KEY } from \"./plugin\";\n\n/**\n * Composable for imperative haptic feedback.\n *\n * Works with or without HapticsPlugin — falls back to built-in presets.\n *\n * Returns plain values (not refs) for isSupported/isIOSSupported to match\n * the React hook's API shape. These don't change at runtime.\n */\nexport function useHaptics() {\n\tconst ctx = inject(HAPTICS_INJECTION_KEY, null);\n\tconst patterns = ctx?.patterns ?? PRESETS;\n\tconst respectReducedMotion = ctx?.respectReducedMotion ?? true;\n\n\tconst prefersReducedMotion = ref(false);\n\n\tonMounted(() => {\n\t\tif (typeof window === \"undefined\" || !window.matchMedia) return;\n\n\t\tconst mql = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n\t\tprefersReducedMotion.value = mql.matches;\n\n\t\tconst onChange = (e: MediaQueryListEvent) => {\n\t\t\tprefersReducedMotion.value = e.matches;\n\t\t};\n\t\tmql.addEventListener(\"change\", onChange);\n\n\t\tonUnmounted(() => {\n\t\t\tmql.removeEventListener(\"change\", onChange);\n\t\t});\n\t});\n\n\tconst trigger = (action: PresetName | (string & {})) => {\n\t\tif (respectReducedMotion && prefersReducedMotion.value) return;\n\n\t\tif (!Object.prototype.hasOwnProperty.call(patterns, action)) return;\n\t\tconst pattern = patterns[action as keyof typeof patterns];\n\t\tif (!pattern) return;\n\n\t\tif (isVibrationSupported()) {\n\t\t\tnavigator.vibrate(toVibrateSequence(pattern));\n\t\t} else if (isIOS()) {\n\t\t\tschedulePattern(pattern);\n\t\t}\n\t};\n\n\tconst cancel = () => {\n\t\tif (isVibrationSupported()) navigator.vibrate(0);\n\t};\n\n\treturn {\n\t\ttrigger,\n\t\tcancel,\n\t\tisSupported: isVibrationSupported(),\n\t\tisIOSSupported: isIOS(),\n\t};\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haptics/vue",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Vue plugin, directive, and composable for haptic feedback. iOS Safari and Android Vibration API.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -49,7 +49,7 @@
49
49
  "composable"
50
50
  ],
51
51
  "dependencies": {
52
- "@haptics/core": "^1.0.0"
52
+ "@haptics/core": "^1.0.1"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "vue": ">=3.3"