@haptics/vue 1.0.0

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 ADDED
@@ -0,0 +1,138 @@
1
+ 'use strict';
2
+
3
+ var core = require('@haptics/core');
4
+ var vue = require('vue');
5
+
6
+ var _patterns = { ...core.PRESETS };
7
+ var _respectReducedMotion = true;
8
+ var _prefersReducedMotion = false;
9
+ function _setConfig(patterns, respectReducedMotion) {
10
+ _patterns = patterns;
11
+ _respectReducedMotion = respectReducedMotion;
12
+ }
13
+ function _getPatterns() {
14
+ return _patterns;
15
+ }
16
+ function _shouldSuppress() {
17
+ return _respectReducedMotion && _prefersReducedMotion;
18
+ }
19
+ function _setPrefersReducedMotion(value) {
20
+ _prefersReducedMotion = value;
21
+ }
22
+
23
+ // src/plugin.ts
24
+ var HAPTICS_INJECTION_KEY = /* @__PURE__ */ Symbol("haptics");
25
+ var HapticsPlugin = {
26
+ install(app, options = {}) {
27
+ const patterns = {
28
+ ...core.PRESETS,
29
+ ...options.patterns
30
+ };
31
+ const respectReducedMotion = options.respectReducedMotion ?? true;
32
+ app.provide(HAPTICS_INJECTION_KEY, { patterns, respectReducedMotion });
33
+ _setConfig(patterns, respectReducedMotion);
34
+ if (typeof document !== "undefined" && core.isIOS()) {
35
+ let prefersReducedMotion = false;
36
+ if (typeof window !== "undefined" && window.matchMedia) {
37
+ const mql = window.matchMedia("(prefers-reduced-motion: reduce)");
38
+ prefersReducedMotion = mql.matches;
39
+ _setPrefersReducedMotion(prefersReducedMotion);
40
+ mql.addEventListener("change", (e) => {
41
+ prefersReducedMotion = e.matches;
42
+ _setPrefersReducedMotion(e.matches);
43
+ });
44
+ }
45
+ const handler = (e) => {
46
+ if (respectReducedMotion && prefersReducedMotion) return;
47
+ const target = e.target?.closest("[data-haptic]");
48
+ if (!target) return;
49
+ const action = target.getAttribute("data-haptic");
50
+ if (action && action in patterns) {
51
+ core.schedulePattern(patterns[action]);
52
+ }
53
+ };
54
+ document.addEventListener("click", handler, {
55
+ capture: true,
56
+ passive: true
57
+ });
58
+ }
59
+ }
60
+ };
61
+ var vHaptic = {
62
+ mounted(el, binding) {
63
+ const action = binding.value;
64
+ if (!action) return;
65
+ el.setAttribute("data-haptic", action);
66
+ if (core.isVibrationSupported()) {
67
+ const handler = () => {
68
+ if (_shouldSuppress()) return;
69
+ const patterns = _getPatterns();
70
+ const pattern = patterns[action];
71
+ if (pattern) {
72
+ navigator.vibrate(core.toVibrateSequence(pattern));
73
+ }
74
+ };
75
+ el.addEventListener("click", handler);
76
+ el.__haptic_handler = handler;
77
+ }
78
+ },
79
+ updated(el, binding) {
80
+ const action = binding.value;
81
+ if (action) {
82
+ el.setAttribute("data-haptic", action);
83
+ } else {
84
+ el.removeAttribute("data-haptic");
85
+ }
86
+ },
87
+ unmounted(el) {
88
+ el.removeAttribute("data-haptic");
89
+ const handler = el.__haptic_handler;
90
+ if (handler) {
91
+ el.removeEventListener("click", handler);
92
+ delete el.__haptic_handler;
93
+ }
94
+ }
95
+ };
96
+ function useHaptics() {
97
+ const ctx = vue.inject(HAPTICS_INJECTION_KEY, null);
98
+ const patterns = ctx?.patterns ?? core.PRESETS;
99
+ const respectReducedMotion = ctx?.respectReducedMotion ?? true;
100
+ const prefersReducedMotion = vue.ref(false);
101
+ vue.onMounted(() => {
102
+ if (typeof window === "undefined" || !window.matchMedia) return;
103
+ const mql = window.matchMedia("(prefers-reduced-motion: reduce)");
104
+ prefersReducedMotion.value = mql.matches;
105
+ const onChange = (e) => {
106
+ prefersReducedMotion.value = e.matches;
107
+ };
108
+ mql.addEventListener("change", onChange);
109
+ vue.onUnmounted(() => {
110
+ mql.removeEventListener("change", onChange);
111
+ });
112
+ });
113
+ const trigger = (action) => {
114
+ if (respectReducedMotion && prefersReducedMotion.value) return;
115
+ const pattern = patterns[action];
116
+ if (!pattern) return;
117
+ if (core.isVibrationSupported()) {
118
+ navigator.vibrate(core.toVibrateSequence(pattern));
119
+ } else if (core.isIOS()) {
120
+ core.schedulePattern(pattern);
121
+ }
122
+ };
123
+ const cancel = () => {
124
+ if (core.isVibrationSupported()) navigator.vibrate(0);
125
+ };
126
+ return {
127
+ trigger,
128
+ cancel,
129
+ isSupported: core.isVibrationSupported(),
130
+ isIOSSupported: core.isIOS()
131
+ };
132
+ }
133
+
134
+ exports.HapticsPlugin = HapticsPlugin;
135
+ exports.useHaptics = useHaptics;
136
+ exports.vHaptic = vHaptic;
137
+ //# sourceMappingURL=index.cjs.map
138
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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":";;;;;AAGA,IAAI,SAAA,GAA2C,EAAE,GAAGA,YAAA,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,YAAAA;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,IAAeC,UAAA,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,UAAAC,oBAAA,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,IAAIC,2BAAqB,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,CAAQC,sBAAA,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,GAAMC,UAAA,CAAO,qBAAA,EAAuB,IAAI,CAAA;AAC9C,EAAA,MAAM,QAAA,GAAW,KAAK,QAAA,IAAYL,YAAAA;AAClC,EAAA,MAAM,oBAAA,GAAuB,KAAK,oBAAA,IAAwB,IAAA;AAE1D,EAAA,MAAM,oBAAA,GAAuBM,QAAI,KAAK,CAAA;AAEtC,EAAAC,aAAA,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,IAAAC,eAAA,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,IAAIL,2BAAqB,EAAG;AAC3B,MAAA,SAAA,CAAU,OAAA,CAAQC,sBAAAA,CAAkB,OAAO,CAAC,CAAA;AAAA,IAC7C,CAAA,MAAA,IAAWH,YAAM,EAAG;AACnB,MAAAC,qBAAgB,OAAO,CAAA;AAAA,IACxB;AAAA,EACD,CAAA;AAEA,EAAA,MAAM,SAAS,MAAM;AACpB,IAAA,IAAIC,yBAAAA,EAAqB,EAAG,SAAA,CAAU,OAAA,CAAQ,CAAC,CAAA;AAAA,EAChD,CAAA;AAEA,EAAA,OAAO;AAAA,IACN,OAAA;AAAA,IACA,MAAA;AAAA,IACA,aAAaA,yBAAAA,EAAqB;AAAA,IAClC,gBAAgBF,UAAAA;AAAM,GACvB;AACD","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"]}
@@ -0,0 +1,48 @@
1
+ import { App, Directive } from 'vue';
2
+ import { HapticPattern, PresetName } from '@haptics/core';
3
+
4
+ interface HapticsPluginOptions {
5
+ /** Custom patterns merged with built-in presets. Same-name customs override. */
6
+ patterns?: Record<string, HapticPattern>;
7
+ /** Suppress haptics when prefers-reduced-motion is active. Default: true */
8
+ respectReducedMotion?: boolean;
9
+ }
10
+ interface HapticsContext {
11
+ patterns: Record<string, HapticPattern>;
12
+ respectReducedMotion: boolean;
13
+ }
14
+ declare const HapticsPlugin: {
15
+ install(app: App, options?: HapticsPluginOptions): void;
16
+ };
17
+
18
+ type HapticValue = string;
19
+ /**
20
+ * v-haptic directive for declarative haptic feedback.
21
+ *
22
+ * Usage:
23
+ * <button v-haptic="'success'">Save</button>
24
+ * <button v-haptic="'impact-heavy'">Delete</button>
25
+ *
26
+ * On iOS: Sets data-haptic attribute so the plugin's capture-phase listener
27
+ * handles it (preserving gesture chain).
28
+ *
29
+ * On Android: Registers a click handler that calls navigator.vibrate().
30
+ */
31
+ declare const vHaptic: Directive<HTMLElement, HapticValue>;
32
+
33
+ /**
34
+ * Composable for imperative haptic feedback.
35
+ *
36
+ * Works with or without HapticsPlugin — falls back to built-in presets.
37
+ *
38
+ * Returns plain values (not refs) for isSupported/isIOSSupported to match
39
+ * the React hook's API shape. These don't change at runtime.
40
+ */
41
+ declare function useHaptics(): {
42
+ trigger: (action: PresetName | (string & {})) => void;
43
+ cancel: () => void;
44
+ isSupported: boolean;
45
+ isIOSSupported: boolean;
46
+ };
47
+
48
+ export { type HapticsContext, HapticsPlugin, type HapticsPluginOptions, useHaptics, vHaptic };
@@ -0,0 +1,48 @@
1
+ import { App, Directive } from 'vue';
2
+ import { HapticPattern, PresetName } from '@haptics/core';
3
+
4
+ interface HapticsPluginOptions {
5
+ /** Custom patterns merged with built-in presets. Same-name customs override. */
6
+ patterns?: Record<string, HapticPattern>;
7
+ /** Suppress haptics when prefers-reduced-motion is active. Default: true */
8
+ respectReducedMotion?: boolean;
9
+ }
10
+ interface HapticsContext {
11
+ patterns: Record<string, HapticPattern>;
12
+ respectReducedMotion: boolean;
13
+ }
14
+ declare const HapticsPlugin: {
15
+ install(app: App, options?: HapticsPluginOptions): void;
16
+ };
17
+
18
+ type HapticValue = string;
19
+ /**
20
+ * v-haptic directive for declarative haptic feedback.
21
+ *
22
+ * Usage:
23
+ * <button v-haptic="'success'">Save</button>
24
+ * <button v-haptic="'impact-heavy'">Delete</button>
25
+ *
26
+ * On iOS: Sets data-haptic attribute so the plugin's capture-phase listener
27
+ * handles it (preserving gesture chain).
28
+ *
29
+ * On Android: Registers a click handler that calls navigator.vibrate().
30
+ */
31
+ declare const vHaptic: Directive<HTMLElement, HapticValue>;
32
+
33
+ /**
34
+ * Composable for imperative haptic feedback.
35
+ *
36
+ * Works with or without HapticsPlugin — falls back to built-in presets.
37
+ *
38
+ * Returns plain values (not refs) for isSupported/isIOSSupported to match
39
+ * the React hook's API shape. These don't change at runtime.
40
+ */
41
+ declare function useHaptics(): {
42
+ trigger: (action: PresetName | (string & {})) => void;
43
+ cancel: () => void;
44
+ isSupported: boolean;
45
+ isIOSSupported: boolean;
46
+ };
47
+
48
+ export { type HapticsContext, HapticsPlugin, type HapticsPluginOptions, useHaptics, vHaptic };
package/dist/index.js ADDED
@@ -0,0 +1,134 @@
1
+ import { PRESETS, isIOS, isVibrationSupported, schedulePattern, toVibrateSequence } from '@haptics/core';
2
+ import { inject, ref, onMounted, onUnmounted } from 'vue';
3
+
4
+ var _patterns = { ...PRESETS };
5
+ var _respectReducedMotion = true;
6
+ var _prefersReducedMotion = false;
7
+ function _setConfig(patterns, respectReducedMotion) {
8
+ _patterns = patterns;
9
+ _respectReducedMotion = respectReducedMotion;
10
+ }
11
+ function _getPatterns() {
12
+ return _patterns;
13
+ }
14
+ function _shouldSuppress() {
15
+ return _respectReducedMotion && _prefersReducedMotion;
16
+ }
17
+ function _setPrefersReducedMotion(value) {
18
+ _prefersReducedMotion = value;
19
+ }
20
+
21
+ // src/plugin.ts
22
+ var HAPTICS_INJECTION_KEY = /* @__PURE__ */ Symbol("haptics");
23
+ var HapticsPlugin = {
24
+ install(app, options = {}) {
25
+ const patterns = {
26
+ ...PRESETS,
27
+ ...options.patterns
28
+ };
29
+ const respectReducedMotion = options.respectReducedMotion ?? true;
30
+ app.provide(HAPTICS_INJECTION_KEY, { patterns, respectReducedMotion });
31
+ _setConfig(patterns, respectReducedMotion);
32
+ if (typeof document !== "undefined" && isIOS()) {
33
+ let prefersReducedMotion = false;
34
+ if (typeof window !== "undefined" && window.matchMedia) {
35
+ const mql = window.matchMedia("(prefers-reduced-motion: reduce)");
36
+ prefersReducedMotion = mql.matches;
37
+ _setPrefersReducedMotion(prefersReducedMotion);
38
+ mql.addEventListener("change", (e) => {
39
+ prefersReducedMotion = e.matches;
40
+ _setPrefersReducedMotion(e.matches);
41
+ });
42
+ }
43
+ const handler = (e) => {
44
+ if (respectReducedMotion && prefersReducedMotion) return;
45
+ const target = e.target?.closest("[data-haptic]");
46
+ if (!target) return;
47
+ const action = target.getAttribute("data-haptic");
48
+ if (action && action in patterns) {
49
+ schedulePattern(patterns[action]);
50
+ }
51
+ };
52
+ document.addEventListener("click", handler, {
53
+ capture: true,
54
+ passive: true
55
+ });
56
+ }
57
+ }
58
+ };
59
+ var vHaptic = {
60
+ mounted(el, binding) {
61
+ const action = binding.value;
62
+ if (!action) return;
63
+ el.setAttribute("data-haptic", action);
64
+ if (isVibrationSupported()) {
65
+ const handler = () => {
66
+ if (_shouldSuppress()) return;
67
+ const patterns = _getPatterns();
68
+ const pattern = patterns[action];
69
+ if (pattern) {
70
+ navigator.vibrate(toVibrateSequence(pattern));
71
+ }
72
+ };
73
+ el.addEventListener("click", handler);
74
+ el.__haptic_handler = handler;
75
+ }
76
+ },
77
+ updated(el, binding) {
78
+ const action = binding.value;
79
+ if (action) {
80
+ el.setAttribute("data-haptic", action);
81
+ } else {
82
+ el.removeAttribute("data-haptic");
83
+ }
84
+ },
85
+ unmounted(el) {
86
+ el.removeAttribute("data-haptic");
87
+ const handler = el.__haptic_handler;
88
+ if (handler) {
89
+ el.removeEventListener("click", handler);
90
+ delete el.__haptic_handler;
91
+ }
92
+ }
93
+ };
94
+ function useHaptics() {
95
+ const ctx = inject(HAPTICS_INJECTION_KEY, null);
96
+ const patterns = ctx?.patterns ?? PRESETS;
97
+ const respectReducedMotion = ctx?.respectReducedMotion ?? true;
98
+ const prefersReducedMotion = ref(false);
99
+ onMounted(() => {
100
+ if (typeof window === "undefined" || !window.matchMedia) return;
101
+ const mql = window.matchMedia("(prefers-reduced-motion: reduce)");
102
+ prefersReducedMotion.value = mql.matches;
103
+ const onChange = (e) => {
104
+ prefersReducedMotion.value = e.matches;
105
+ };
106
+ mql.addEventListener("change", onChange);
107
+ onUnmounted(() => {
108
+ mql.removeEventListener("change", onChange);
109
+ });
110
+ });
111
+ const trigger = (action) => {
112
+ if (respectReducedMotion && prefersReducedMotion.value) return;
113
+ const pattern = patterns[action];
114
+ if (!pattern) return;
115
+ if (isVibrationSupported()) {
116
+ navigator.vibrate(toVibrateSequence(pattern));
117
+ } else if (isIOS()) {
118
+ schedulePattern(pattern);
119
+ }
120
+ };
121
+ const cancel = () => {
122
+ if (isVibrationSupported()) navigator.vibrate(0);
123
+ };
124
+ return {
125
+ trigger,
126
+ cancel,
127
+ isSupported: isVibrationSupported(),
128
+ isIOSSupported: isIOS()
129
+ };
130
+ }
131
+
132
+ export { HapticsPlugin, useHaptics, vHaptic };
133
+ //# sourceMappingURL=index.js.map
134
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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":"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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\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"]}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@haptics/vue",
3
+ "version": "1.0.0",
4
+ "description": "Vue plugin, directive, and composable for haptic feedback. iOS Safari and Android Vibration API.",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "sideEffects": false,
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "dev": "tsup --watch",
28
+ "type-check": "tsc --noEmit",
29
+ "test": "vitest run"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/howdoiusekeyboard/haptics.git",
37
+ "directory": "packages/vue"
38
+ },
39
+ "author": "Howdoiusekeyboard",
40
+ "license": "MIT",
41
+ "keywords": [
42
+ "haptics",
43
+ "vue",
44
+ "haptic-feedback",
45
+ "vibration",
46
+ "ios",
47
+ "android",
48
+ "directive",
49
+ "composable"
50
+ ],
51
+ "dependencies": {
52
+ "@haptics/core": "^1.0.0"
53
+ },
54
+ "peerDependencies": {
55
+ "vue": ">=3.3"
56
+ },
57
+ "devDependencies": {
58
+ "@vue/test-utils": "^2.4.0",
59
+ "jsdom": "^29.0.0",
60
+ "tsup": "^8.0.0",
61
+ "vitest": "^4.1.0",
62
+ "vue": "^3.5.0"
63
+ }
64
+ }