@hapticjs/react 0.1.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 react = require('react');
4
+ var core = require('@hapticjs/core');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ // src/hooks/use-haptic.ts
8
+ var HapticContext = react.createContext(null);
9
+ function HapticProvider({ config, engine, children }) {
10
+ const value = react.useMemo(() => {
11
+ if (engine) return { engine };
12
+ return { engine: core.HapticEngine.create(config) };
13
+ }, [engine, config]);
14
+ return /* @__PURE__ */ jsxRuntime.jsx(HapticContext.Provider, { value, children });
15
+ }
16
+ function useHapticEngine() {
17
+ const context = react.useContext(HapticContext);
18
+ if (context) return context.engine;
19
+ return core.HapticEngine.create();
20
+ }
21
+
22
+ // src/hooks/use-haptic.ts
23
+ function useHaptic(effect) {
24
+ const engine = useHapticEngine();
25
+ const trigger = react.useCallback(async () => {
26
+ if (!effect) return;
27
+ if (effect in engine) {
28
+ const method = engine[effect];
29
+ if (typeof method === "function") {
30
+ await method.call(engine);
31
+ return;
32
+ }
33
+ }
34
+ await engine.play(effect);
35
+ }, [engine, effect]);
36
+ const actions = react.useMemo(
37
+ () => ({
38
+ tap: (intensity) => engine.tap(intensity),
39
+ doubleTap: (intensity) => engine.doubleTap(intensity),
40
+ longPress: (intensity) => engine.longPress(intensity),
41
+ success: () => engine.success(),
42
+ warning: () => engine.warning(),
43
+ error: () => engine.error(),
44
+ selection: () => engine.selection(),
45
+ toggle: (on) => engine.toggle(on),
46
+ play: (pattern) => engine.play(pattern),
47
+ vibrate: (duration, intensity) => engine.vibrate(duration, intensity),
48
+ isSupported: engine.isSupported
49
+ }),
50
+ [engine]
51
+ );
52
+ if (effect === void 0) {
53
+ return actions;
54
+ }
55
+ return {
56
+ trigger,
57
+ isSupported: engine.isSupported,
58
+ engine
59
+ };
60
+ }
61
+ function useHapticGesture(config) {
62
+ const engine = useHapticEngine();
63
+ const pressTimer = react.useRef(null);
64
+ const isLongPress = react.useRef(false);
65
+ const threshold = config.longPressThreshold ?? 500;
66
+ const playEffect = react.useCallback(
67
+ async (effect) => {
68
+ if (effect in engine) {
69
+ const method = engine[effect];
70
+ if (typeof method === "function") {
71
+ await method.call(engine);
72
+ return;
73
+ }
74
+ }
75
+ await engine.play(effect);
76
+ },
77
+ [engine]
78
+ );
79
+ const onPointerDown = react.useCallback(() => {
80
+ isLongPress.current = false;
81
+ if (config.onLongPress) {
82
+ pressTimer.current = setTimeout(() => {
83
+ isLongPress.current = true;
84
+ if (config.onLongPress) {
85
+ playEffect(config.onLongPress);
86
+ }
87
+ }, threshold);
88
+ }
89
+ }, [config.onLongPress, threshold, playEffect]);
90
+ const onPointerUp = react.useCallback(() => {
91
+ if (pressTimer.current) {
92
+ clearTimeout(pressTimer.current);
93
+ pressTimer.current = null;
94
+ }
95
+ if (!isLongPress.current && config.onTap) {
96
+ playEffect(config.onTap);
97
+ }
98
+ }, [config.onTap, playEffect]);
99
+ const onPointerCancel = react.useCallback(() => {
100
+ if (pressTimer.current) {
101
+ clearTimeout(pressTimer.current);
102
+ pressTimer.current = null;
103
+ }
104
+ }, []);
105
+ const onPointerEnter = react.useCallback(() => {
106
+ if (config.onHover) {
107
+ playEffect(config.onHover);
108
+ }
109
+ }, [config.onHover, playEffect]);
110
+ return {
111
+ onPointerDown,
112
+ onPointerUp,
113
+ onPointerCancel,
114
+ ...config.onHover ? { onPointerEnter } : {}
115
+ };
116
+ }
117
+ function HapticButton({
118
+ effect = "tap",
119
+ pattern,
120
+ onClick,
121
+ children,
122
+ ...props
123
+ }) {
124
+ const { trigger } = useHaptic(pattern ?? effect);
125
+ const handleClick = async (e) => {
126
+ await trigger();
127
+ onClick?.(e);
128
+ };
129
+ return /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: handleClick, ...props, children });
130
+ }
131
+
132
+ exports.HapticButton = HapticButton;
133
+ exports.HapticProvider = HapticProvider;
134
+ exports.useHaptic = useHaptic;
135
+ exports.useHapticEngine = useHapticEngine;
136
+ exports.useHapticGesture = useHapticGesture;
137
+ //# sourceMappingURL=index.cjs.map
138
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/context/haptic-provider.tsx","../src/hooks/use-haptic.ts","../src/hooks/use-haptic-gesture.ts","../src/components/haptic-button.tsx"],"names":["createContext","useMemo","HapticEngine","jsx","useContext","useCallback","useRef"],"mappings":"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","file":"index.cjs","sourcesContent":["import { createContext, useContext, useMemo } from 'react';\nimport { HapticEngine } from '@hapticjs/core';\nimport type { HapticConfig } from '@hapticjs/core';\n\nexport interface HapticContextValue {\n engine: HapticEngine;\n}\n\nconst HapticContext = createContext<HapticContextValue | null>(null);\n\nexport interface HapticProviderProps {\n config?: Partial<HapticConfig>;\n engine?: HapticEngine;\n children: React.ReactNode;\n}\n\n/**\n * Provides a shared HapticEngine to all child components.\n *\n * Usage:\n * <HapticProvider config={{ intensity: 0.8 }}>\n * <App />\n * </HapticProvider>\n */\nexport function HapticProvider({ config, engine, children }: HapticProviderProps) {\n const value = useMemo<HapticContextValue>(() => {\n if (engine) return { engine };\n return { engine: HapticEngine.create(config) };\n }, [engine, config]);\n\n return (\n <HapticContext.Provider value={value}>\n {children}\n </HapticContext.Provider>\n );\n}\n\n/** Get the HapticEngine from context, or create a default one */\nexport function useHapticEngine(): HapticEngine {\n const context = useContext(HapticContext);\n if (context) return context.engine;\n\n // No provider — return the default singleton\n return HapticEngine.create();\n}\n","import { useCallback, useMemo } from 'react';\nimport type { HapticPattern, HapticStep, SemanticEffect } from '@hapticjs/core';\nimport { useHapticEngine } from '../context/haptic-provider';\n\nexport interface UseHapticReturn {\n /** Trigger the specified effect */\n trigger: () => Promise<void>;\n /** Whether haptics are supported on this device */\n isSupported: boolean;\n /** The underlying engine instance */\n engine: ReturnType<typeof useHapticEngine>;\n}\n\nexport interface UseHapticActions {\n tap: (intensity?: number) => Promise<void>;\n doubleTap: (intensity?: number) => Promise<void>;\n longPress: (intensity?: number) => Promise<void>;\n success: () => Promise<void>;\n warning: () => Promise<void>;\n error: () => Promise<void>;\n selection: () => Promise<void>;\n toggle: (on: boolean) => Promise<void>;\n play: (pattern: string | HapticPattern | HapticStep[]) => Promise<void>;\n vibrate: (duration: number, intensity?: number) => Promise<void>;\n isSupported: boolean;\n}\n\n/**\n * React hook for haptic feedback.\n *\n * With a preset:\n * const { trigger, isSupported } = useHaptic('success');\n * <button onClick={trigger}>Yay!</button>\n *\n * Without preset (full API):\n * const haptic = useHaptic();\n * haptic.tap();\n * haptic.play('~~..##');\n */\nexport function useHaptic(): UseHapticActions;\nexport function useHaptic(effect: SemanticEffect | string): UseHapticReturn;\nexport function useHaptic(effect?: SemanticEffect | string): UseHapticReturn | UseHapticActions {\n const engine = useHapticEngine();\n\n const trigger = useCallback(async () => {\n if (!effect) return;\n\n // Check if it's a semantic effect\n if (effect in engine) {\n const method = engine[effect as keyof typeof engine];\n if (typeof method === 'function') {\n await (method as () => Promise<void>).call(engine);\n return;\n }\n }\n\n // Try as HPL pattern\n await engine.play(effect);\n }, [engine, effect]);\n\n const actions = useMemo<UseHapticActions>(\n () => ({\n tap: (intensity?: number) => engine.tap(intensity),\n doubleTap: (intensity?: number) => engine.doubleTap(intensity),\n longPress: (intensity?: number) => engine.longPress(intensity),\n success: () => engine.success(),\n warning: () => engine.warning(),\n error: () => engine.error(),\n selection: () => engine.selection(),\n toggle: (on: boolean) => engine.toggle(on),\n play: (pattern: string | HapticPattern | HapticStep[]) => engine.play(pattern),\n vibrate: (duration: number, intensity?: number) => engine.vibrate(duration, intensity),\n isSupported: engine.isSupported,\n }),\n [engine],\n );\n\n if (effect === undefined) {\n return actions;\n }\n\n return {\n trigger,\n isSupported: engine.isSupported,\n engine,\n };\n}\n","import { useCallback, useRef } from 'react';\nimport type { SemanticEffect } from '@hapticjs/core';\nimport { useHapticEngine } from '../context/haptic-provider';\n\nexport interface HapticGestureConfig {\n /** Effect to play on tap/click */\n onTap?: SemanticEffect | string;\n /** Effect to play on long press (>500ms) */\n onLongPress?: SemanticEffect | string;\n /** Effect to play on pointer enter (hover) */\n onHover?: SemanticEffect | string;\n /** Long press threshold in ms */\n longPressThreshold?: number;\n}\n\n/**\n * Hook that binds haptic feedback to pointer/touch gestures.\n *\n * Usage:\n * const gestureProps = useHapticGesture({\n * onTap: 'tap',\n * onLongPress: 'heavy',\n * });\n * <div {...gestureProps}>Press me</div>\n */\nexport function useHapticGesture(config: HapticGestureConfig) {\n const engine = useHapticEngine();\n const pressTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const isLongPress = useRef(false);\n const threshold = config.longPressThreshold ?? 500;\n\n const playEffect = useCallback(\n async (effect: SemanticEffect | string) => {\n if (effect in engine) {\n const method = engine[effect as keyof typeof engine];\n if (typeof method === 'function') {\n await (method as () => Promise<void>).call(engine);\n return;\n }\n }\n await engine.play(effect);\n },\n [engine],\n );\n\n const onPointerDown = useCallback(() => {\n isLongPress.current = false;\n\n if (config.onLongPress) {\n pressTimer.current = setTimeout(() => {\n isLongPress.current = true;\n if (config.onLongPress) {\n playEffect(config.onLongPress);\n }\n }, threshold);\n }\n }, [config.onLongPress, threshold, playEffect]);\n\n const onPointerUp = useCallback(() => {\n if (pressTimer.current) {\n clearTimeout(pressTimer.current);\n pressTimer.current = null;\n }\n\n if (!isLongPress.current && config.onTap) {\n playEffect(config.onTap);\n }\n }, [config.onTap, playEffect]);\n\n const onPointerCancel = useCallback(() => {\n if (pressTimer.current) {\n clearTimeout(pressTimer.current);\n pressTimer.current = null;\n }\n }, []);\n\n const onPointerEnter = useCallback(() => {\n if (config.onHover) {\n playEffect(config.onHover);\n }\n }, [config.onHover, playEffect]);\n\n return {\n onPointerDown,\n onPointerUp,\n onPointerCancel,\n ...(config.onHover ? { onPointerEnter } : {}),\n };\n}\n","import type { SemanticEffect } from '@hapticjs/core';\nimport { useHaptic } from '../hooks/use-haptic';\n\nexport interface HapticButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Haptic effect to trigger on click */\n effect?: SemanticEffect | string;\n /** HPL pattern string to play on click */\n pattern?: string;\n}\n\n/**\n * Button component with built-in haptic feedback.\n *\n * Usage:\n * <HapticButton effect=\"tap\">Click me</HapticButton>\n * <HapticButton pattern=\"~~..##\">Custom feel</HapticButton>\n */\nexport function HapticButton({\n effect = 'tap',\n pattern,\n onClick,\n children,\n ...props\n}: HapticButtonProps) {\n const { trigger } = useHaptic(pattern ?? effect);\n\n const handleClick = async (e: React.MouseEvent<HTMLButtonElement>) => {\n await trigger();\n onClick?.(e);\n };\n\n return (\n <button onClick={handleClick} {...props}>\n {children}\n </button>\n );\n}\n"]}
@@ -0,0 +1,102 @@
1
+ import { HapticEngine, HapticConfig, HapticPattern, HapticStep, SemanticEffect } from '@hapticjs/core';
2
+ import * as react_jsx_runtime from 'react/jsx-runtime';
3
+
4
+ interface HapticContextValue {
5
+ engine: HapticEngine;
6
+ }
7
+ interface HapticProviderProps {
8
+ config?: Partial<HapticConfig>;
9
+ engine?: HapticEngine;
10
+ children: React.ReactNode;
11
+ }
12
+ /**
13
+ * Provides a shared HapticEngine to all child components.
14
+ *
15
+ * Usage:
16
+ * <HapticProvider config={{ intensity: 0.8 }}>
17
+ * <App />
18
+ * </HapticProvider>
19
+ */
20
+ declare function HapticProvider({ config, engine, children }: HapticProviderProps): react_jsx_runtime.JSX.Element;
21
+ /** Get the HapticEngine from context, or create a default one */
22
+ declare function useHapticEngine(): HapticEngine;
23
+
24
+ interface UseHapticReturn {
25
+ /** Trigger the specified effect */
26
+ trigger: () => Promise<void>;
27
+ /** Whether haptics are supported on this device */
28
+ isSupported: boolean;
29
+ /** The underlying engine instance */
30
+ engine: ReturnType<typeof useHapticEngine>;
31
+ }
32
+ interface UseHapticActions {
33
+ tap: (intensity?: number) => Promise<void>;
34
+ doubleTap: (intensity?: number) => Promise<void>;
35
+ longPress: (intensity?: number) => Promise<void>;
36
+ success: () => Promise<void>;
37
+ warning: () => Promise<void>;
38
+ error: () => Promise<void>;
39
+ selection: () => Promise<void>;
40
+ toggle: (on: boolean) => Promise<void>;
41
+ play: (pattern: string | HapticPattern | HapticStep[]) => Promise<void>;
42
+ vibrate: (duration: number, intensity?: number) => Promise<void>;
43
+ isSupported: boolean;
44
+ }
45
+ /**
46
+ * React hook for haptic feedback.
47
+ *
48
+ * With a preset:
49
+ * const { trigger, isSupported } = useHaptic('success');
50
+ * <button onClick={trigger}>Yay!</button>
51
+ *
52
+ * Without preset (full API):
53
+ * const haptic = useHaptic();
54
+ * haptic.tap();
55
+ * haptic.play('~~..##');
56
+ */
57
+ declare function useHaptic(): UseHapticActions;
58
+ declare function useHaptic(effect: SemanticEffect | string): UseHapticReturn;
59
+
60
+ interface HapticGestureConfig {
61
+ /** Effect to play on tap/click */
62
+ onTap?: SemanticEffect | string;
63
+ /** Effect to play on long press (>500ms) */
64
+ onLongPress?: SemanticEffect | string;
65
+ /** Effect to play on pointer enter (hover) */
66
+ onHover?: SemanticEffect | string;
67
+ /** Long press threshold in ms */
68
+ longPressThreshold?: number;
69
+ }
70
+ /**
71
+ * Hook that binds haptic feedback to pointer/touch gestures.
72
+ *
73
+ * Usage:
74
+ * const gestureProps = useHapticGesture({
75
+ * onTap: 'tap',
76
+ * onLongPress: 'heavy',
77
+ * });
78
+ * <div {...gestureProps}>Press me</div>
79
+ */
80
+ declare function useHapticGesture(config: HapticGestureConfig): {
81
+ onPointerEnter?: (() => void) | undefined;
82
+ onPointerDown: () => void;
83
+ onPointerUp: () => void;
84
+ onPointerCancel: () => void;
85
+ };
86
+
87
+ interface HapticButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
88
+ /** Haptic effect to trigger on click */
89
+ effect?: SemanticEffect | string;
90
+ /** HPL pattern string to play on click */
91
+ pattern?: string;
92
+ }
93
+ /**
94
+ * Button component with built-in haptic feedback.
95
+ *
96
+ * Usage:
97
+ * <HapticButton effect="tap">Click me</HapticButton>
98
+ * <HapticButton pattern="~~..##">Custom feel</HapticButton>
99
+ */
100
+ declare function HapticButton({ effect, pattern, onClick, children, ...props }: HapticButtonProps): react_jsx_runtime.JSX.Element;
101
+
102
+ export { HapticButton, type HapticButtonProps, type HapticContextValue, type HapticGestureConfig, HapticProvider, type HapticProviderProps, type UseHapticActions, type UseHapticReturn, useHaptic, useHapticEngine, useHapticGesture };
@@ -0,0 +1,102 @@
1
+ import { HapticEngine, HapticConfig, HapticPattern, HapticStep, SemanticEffect } from '@hapticjs/core';
2
+ import * as react_jsx_runtime from 'react/jsx-runtime';
3
+
4
+ interface HapticContextValue {
5
+ engine: HapticEngine;
6
+ }
7
+ interface HapticProviderProps {
8
+ config?: Partial<HapticConfig>;
9
+ engine?: HapticEngine;
10
+ children: React.ReactNode;
11
+ }
12
+ /**
13
+ * Provides a shared HapticEngine to all child components.
14
+ *
15
+ * Usage:
16
+ * <HapticProvider config={{ intensity: 0.8 }}>
17
+ * <App />
18
+ * </HapticProvider>
19
+ */
20
+ declare function HapticProvider({ config, engine, children }: HapticProviderProps): react_jsx_runtime.JSX.Element;
21
+ /** Get the HapticEngine from context, or create a default one */
22
+ declare function useHapticEngine(): HapticEngine;
23
+
24
+ interface UseHapticReturn {
25
+ /** Trigger the specified effect */
26
+ trigger: () => Promise<void>;
27
+ /** Whether haptics are supported on this device */
28
+ isSupported: boolean;
29
+ /** The underlying engine instance */
30
+ engine: ReturnType<typeof useHapticEngine>;
31
+ }
32
+ interface UseHapticActions {
33
+ tap: (intensity?: number) => Promise<void>;
34
+ doubleTap: (intensity?: number) => Promise<void>;
35
+ longPress: (intensity?: number) => Promise<void>;
36
+ success: () => Promise<void>;
37
+ warning: () => Promise<void>;
38
+ error: () => Promise<void>;
39
+ selection: () => Promise<void>;
40
+ toggle: (on: boolean) => Promise<void>;
41
+ play: (pattern: string | HapticPattern | HapticStep[]) => Promise<void>;
42
+ vibrate: (duration: number, intensity?: number) => Promise<void>;
43
+ isSupported: boolean;
44
+ }
45
+ /**
46
+ * React hook for haptic feedback.
47
+ *
48
+ * With a preset:
49
+ * const { trigger, isSupported } = useHaptic('success');
50
+ * <button onClick={trigger}>Yay!</button>
51
+ *
52
+ * Without preset (full API):
53
+ * const haptic = useHaptic();
54
+ * haptic.tap();
55
+ * haptic.play('~~..##');
56
+ */
57
+ declare function useHaptic(): UseHapticActions;
58
+ declare function useHaptic(effect: SemanticEffect | string): UseHapticReturn;
59
+
60
+ interface HapticGestureConfig {
61
+ /** Effect to play on tap/click */
62
+ onTap?: SemanticEffect | string;
63
+ /** Effect to play on long press (>500ms) */
64
+ onLongPress?: SemanticEffect | string;
65
+ /** Effect to play on pointer enter (hover) */
66
+ onHover?: SemanticEffect | string;
67
+ /** Long press threshold in ms */
68
+ longPressThreshold?: number;
69
+ }
70
+ /**
71
+ * Hook that binds haptic feedback to pointer/touch gestures.
72
+ *
73
+ * Usage:
74
+ * const gestureProps = useHapticGesture({
75
+ * onTap: 'tap',
76
+ * onLongPress: 'heavy',
77
+ * });
78
+ * <div {...gestureProps}>Press me</div>
79
+ */
80
+ declare function useHapticGesture(config: HapticGestureConfig): {
81
+ onPointerEnter?: (() => void) | undefined;
82
+ onPointerDown: () => void;
83
+ onPointerUp: () => void;
84
+ onPointerCancel: () => void;
85
+ };
86
+
87
+ interface HapticButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
88
+ /** Haptic effect to trigger on click */
89
+ effect?: SemanticEffect | string;
90
+ /** HPL pattern string to play on click */
91
+ pattern?: string;
92
+ }
93
+ /**
94
+ * Button component with built-in haptic feedback.
95
+ *
96
+ * Usage:
97
+ * <HapticButton effect="tap">Click me</HapticButton>
98
+ * <HapticButton pattern="~~..##">Custom feel</HapticButton>
99
+ */
100
+ declare function HapticButton({ effect, pattern, onClick, children, ...props }: HapticButtonProps): react_jsx_runtime.JSX.Element;
101
+
102
+ export { HapticButton, type HapticButtonProps, type HapticContextValue, type HapticGestureConfig, HapticProvider, type HapticProviderProps, type UseHapticActions, type UseHapticReturn, useHaptic, useHapticEngine, useHapticGesture };
package/dist/index.js ADDED
@@ -0,0 +1,132 @@
1
+ import { createContext, useMemo, useContext, useCallback, useRef } from 'react';
2
+ import { HapticEngine } from '@hapticjs/core';
3
+ import { jsx } from 'react/jsx-runtime';
4
+
5
+ // src/hooks/use-haptic.ts
6
+ var HapticContext = createContext(null);
7
+ function HapticProvider({ config, engine, children }) {
8
+ const value = useMemo(() => {
9
+ if (engine) return { engine };
10
+ return { engine: HapticEngine.create(config) };
11
+ }, [engine, config]);
12
+ return /* @__PURE__ */ jsx(HapticContext.Provider, { value, children });
13
+ }
14
+ function useHapticEngine() {
15
+ const context = useContext(HapticContext);
16
+ if (context) return context.engine;
17
+ return HapticEngine.create();
18
+ }
19
+
20
+ // src/hooks/use-haptic.ts
21
+ function useHaptic(effect) {
22
+ const engine = useHapticEngine();
23
+ const trigger = useCallback(async () => {
24
+ if (!effect) return;
25
+ if (effect in engine) {
26
+ const method = engine[effect];
27
+ if (typeof method === "function") {
28
+ await method.call(engine);
29
+ return;
30
+ }
31
+ }
32
+ await engine.play(effect);
33
+ }, [engine, effect]);
34
+ const actions = useMemo(
35
+ () => ({
36
+ tap: (intensity) => engine.tap(intensity),
37
+ doubleTap: (intensity) => engine.doubleTap(intensity),
38
+ longPress: (intensity) => engine.longPress(intensity),
39
+ success: () => engine.success(),
40
+ warning: () => engine.warning(),
41
+ error: () => engine.error(),
42
+ selection: () => engine.selection(),
43
+ toggle: (on) => engine.toggle(on),
44
+ play: (pattern) => engine.play(pattern),
45
+ vibrate: (duration, intensity) => engine.vibrate(duration, intensity),
46
+ isSupported: engine.isSupported
47
+ }),
48
+ [engine]
49
+ );
50
+ if (effect === void 0) {
51
+ return actions;
52
+ }
53
+ return {
54
+ trigger,
55
+ isSupported: engine.isSupported,
56
+ engine
57
+ };
58
+ }
59
+ function useHapticGesture(config) {
60
+ const engine = useHapticEngine();
61
+ const pressTimer = useRef(null);
62
+ const isLongPress = useRef(false);
63
+ const threshold = config.longPressThreshold ?? 500;
64
+ const playEffect = useCallback(
65
+ async (effect) => {
66
+ if (effect in engine) {
67
+ const method = engine[effect];
68
+ if (typeof method === "function") {
69
+ await method.call(engine);
70
+ return;
71
+ }
72
+ }
73
+ await engine.play(effect);
74
+ },
75
+ [engine]
76
+ );
77
+ const onPointerDown = useCallback(() => {
78
+ isLongPress.current = false;
79
+ if (config.onLongPress) {
80
+ pressTimer.current = setTimeout(() => {
81
+ isLongPress.current = true;
82
+ if (config.onLongPress) {
83
+ playEffect(config.onLongPress);
84
+ }
85
+ }, threshold);
86
+ }
87
+ }, [config.onLongPress, threshold, playEffect]);
88
+ const onPointerUp = useCallback(() => {
89
+ if (pressTimer.current) {
90
+ clearTimeout(pressTimer.current);
91
+ pressTimer.current = null;
92
+ }
93
+ if (!isLongPress.current && config.onTap) {
94
+ playEffect(config.onTap);
95
+ }
96
+ }, [config.onTap, playEffect]);
97
+ const onPointerCancel = useCallback(() => {
98
+ if (pressTimer.current) {
99
+ clearTimeout(pressTimer.current);
100
+ pressTimer.current = null;
101
+ }
102
+ }, []);
103
+ const onPointerEnter = useCallback(() => {
104
+ if (config.onHover) {
105
+ playEffect(config.onHover);
106
+ }
107
+ }, [config.onHover, playEffect]);
108
+ return {
109
+ onPointerDown,
110
+ onPointerUp,
111
+ onPointerCancel,
112
+ ...config.onHover ? { onPointerEnter } : {}
113
+ };
114
+ }
115
+ function HapticButton({
116
+ effect = "tap",
117
+ pattern,
118
+ onClick,
119
+ children,
120
+ ...props
121
+ }) {
122
+ const { trigger } = useHaptic(pattern ?? effect);
123
+ const handleClick = async (e) => {
124
+ await trigger();
125
+ onClick?.(e);
126
+ };
127
+ return /* @__PURE__ */ jsx("button", { onClick: handleClick, ...props, children });
128
+ }
129
+
130
+ export { HapticButton, HapticProvider, useHaptic, useHapticEngine, useHapticGesture };
131
+ //# sourceMappingURL=index.js.map
132
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/context/haptic-provider.tsx","../src/hooks/use-haptic.ts","../src/hooks/use-haptic-gesture.ts","../src/components/haptic-button.tsx"],"names":["useMemo","useCallback","jsx"],"mappings":"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","file":"index.js","sourcesContent":["import { createContext, useContext, useMemo } from 'react';\nimport { HapticEngine } from '@hapticjs/core';\nimport type { HapticConfig } from '@hapticjs/core';\n\nexport interface HapticContextValue {\n engine: HapticEngine;\n}\n\nconst HapticContext = createContext<HapticContextValue | null>(null);\n\nexport interface HapticProviderProps {\n config?: Partial<HapticConfig>;\n engine?: HapticEngine;\n children: React.ReactNode;\n}\n\n/**\n * Provides a shared HapticEngine to all child components.\n *\n * Usage:\n * <HapticProvider config={{ intensity: 0.8 }}>\n * <App />\n * </HapticProvider>\n */\nexport function HapticProvider({ config, engine, children }: HapticProviderProps) {\n const value = useMemo<HapticContextValue>(() => {\n if (engine) return { engine };\n return { engine: HapticEngine.create(config) };\n }, [engine, config]);\n\n return (\n <HapticContext.Provider value={value}>\n {children}\n </HapticContext.Provider>\n );\n}\n\n/** Get the HapticEngine from context, or create a default one */\nexport function useHapticEngine(): HapticEngine {\n const context = useContext(HapticContext);\n if (context) return context.engine;\n\n // No provider — return the default singleton\n return HapticEngine.create();\n}\n","import { useCallback, useMemo } from 'react';\nimport type { HapticPattern, HapticStep, SemanticEffect } from '@hapticjs/core';\nimport { useHapticEngine } from '../context/haptic-provider';\n\nexport interface UseHapticReturn {\n /** Trigger the specified effect */\n trigger: () => Promise<void>;\n /** Whether haptics are supported on this device */\n isSupported: boolean;\n /** The underlying engine instance */\n engine: ReturnType<typeof useHapticEngine>;\n}\n\nexport interface UseHapticActions {\n tap: (intensity?: number) => Promise<void>;\n doubleTap: (intensity?: number) => Promise<void>;\n longPress: (intensity?: number) => Promise<void>;\n success: () => Promise<void>;\n warning: () => Promise<void>;\n error: () => Promise<void>;\n selection: () => Promise<void>;\n toggle: (on: boolean) => Promise<void>;\n play: (pattern: string | HapticPattern | HapticStep[]) => Promise<void>;\n vibrate: (duration: number, intensity?: number) => Promise<void>;\n isSupported: boolean;\n}\n\n/**\n * React hook for haptic feedback.\n *\n * With a preset:\n * const { trigger, isSupported } = useHaptic('success');\n * <button onClick={trigger}>Yay!</button>\n *\n * Without preset (full API):\n * const haptic = useHaptic();\n * haptic.tap();\n * haptic.play('~~..##');\n */\nexport function useHaptic(): UseHapticActions;\nexport function useHaptic(effect: SemanticEffect | string): UseHapticReturn;\nexport function useHaptic(effect?: SemanticEffect | string): UseHapticReturn | UseHapticActions {\n const engine = useHapticEngine();\n\n const trigger = useCallback(async () => {\n if (!effect) return;\n\n // Check if it's a semantic effect\n if (effect in engine) {\n const method = engine[effect as keyof typeof engine];\n if (typeof method === 'function') {\n await (method as () => Promise<void>).call(engine);\n return;\n }\n }\n\n // Try as HPL pattern\n await engine.play(effect);\n }, [engine, effect]);\n\n const actions = useMemo<UseHapticActions>(\n () => ({\n tap: (intensity?: number) => engine.tap(intensity),\n doubleTap: (intensity?: number) => engine.doubleTap(intensity),\n longPress: (intensity?: number) => engine.longPress(intensity),\n success: () => engine.success(),\n warning: () => engine.warning(),\n error: () => engine.error(),\n selection: () => engine.selection(),\n toggle: (on: boolean) => engine.toggle(on),\n play: (pattern: string | HapticPattern | HapticStep[]) => engine.play(pattern),\n vibrate: (duration: number, intensity?: number) => engine.vibrate(duration, intensity),\n isSupported: engine.isSupported,\n }),\n [engine],\n );\n\n if (effect === undefined) {\n return actions;\n }\n\n return {\n trigger,\n isSupported: engine.isSupported,\n engine,\n };\n}\n","import { useCallback, useRef } from 'react';\nimport type { SemanticEffect } from '@hapticjs/core';\nimport { useHapticEngine } from '../context/haptic-provider';\n\nexport interface HapticGestureConfig {\n /** Effect to play on tap/click */\n onTap?: SemanticEffect | string;\n /** Effect to play on long press (>500ms) */\n onLongPress?: SemanticEffect | string;\n /** Effect to play on pointer enter (hover) */\n onHover?: SemanticEffect | string;\n /** Long press threshold in ms */\n longPressThreshold?: number;\n}\n\n/**\n * Hook that binds haptic feedback to pointer/touch gestures.\n *\n * Usage:\n * const gestureProps = useHapticGesture({\n * onTap: 'tap',\n * onLongPress: 'heavy',\n * });\n * <div {...gestureProps}>Press me</div>\n */\nexport function useHapticGesture(config: HapticGestureConfig) {\n const engine = useHapticEngine();\n const pressTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const isLongPress = useRef(false);\n const threshold = config.longPressThreshold ?? 500;\n\n const playEffect = useCallback(\n async (effect: SemanticEffect | string) => {\n if (effect in engine) {\n const method = engine[effect as keyof typeof engine];\n if (typeof method === 'function') {\n await (method as () => Promise<void>).call(engine);\n return;\n }\n }\n await engine.play(effect);\n },\n [engine],\n );\n\n const onPointerDown = useCallback(() => {\n isLongPress.current = false;\n\n if (config.onLongPress) {\n pressTimer.current = setTimeout(() => {\n isLongPress.current = true;\n if (config.onLongPress) {\n playEffect(config.onLongPress);\n }\n }, threshold);\n }\n }, [config.onLongPress, threshold, playEffect]);\n\n const onPointerUp = useCallback(() => {\n if (pressTimer.current) {\n clearTimeout(pressTimer.current);\n pressTimer.current = null;\n }\n\n if (!isLongPress.current && config.onTap) {\n playEffect(config.onTap);\n }\n }, [config.onTap, playEffect]);\n\n const onPointerCancel = useCallback(() => {\n if (pressTimer.current) {\n clearTimeout(pressTimer.current);\n pressTimer.current = null;\n }\n }, []);\n\n const onPointerEnter = useCallback(() => {\n if (config.onHover) {\n playEffect(config.onHover);\n }\n }, [config.onHover, playEffect]);\n\n return {\n onPointerDown,\n onPointerUp,\n onPointerCancel,\n ...(config.onHover ? { onPointerEnter } : {}),\n };\n}\n","import type { SemanticEffect } from '@hapticjs/core';\nimport { useHaptic } from '../hooks/use-haptic';\n\nexport interface HapticButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Haptic effect to trigger on click */\n effect?: SemanticEffect | string;\n /** HPL pattern string to play on click */\n pattern?: string;\n}\n\n/**\n * Button component with built-in haptic feedback.\n *\n * Usage:\n * <HapticButton effect=\"tap\">Click me</HapticButton>\n * <HapticButton pattern=\"~~..##\">Custom feel</HapticButton>\n */\nexport function HapticButton({\n effect = 'tap',\n pattern,\n onClick,\n children,\n ...props\n}: HapticButtonProps) {\n const { trigger } = useHaptic(pattern ?? effect);\n\n const handleClick = async (e: React.MouseEvent<HTMLButtonElement>) => {\n await trigger();\n onClick?.(e);\n };\n\n return (\n <button onClick={handleClick} {...props}>\n {children}\n </button>\n );\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@hapticjs/react",
3
+ "version": "0.1.0",
4
+ "description": "React hooks and components for Feelback haptic engine",
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
+ "peerDependencies": {
26
+ "react": ">=17.0.0",
27
+ "@hapticjs/core": "0.1.0"
28
+ },
29
+ "devDependencies": {
30
+ "react": "^19.0.0",
31
+ "@types/react": "^19.0.0",
32
+ "@hapticjs/core": "0.1.0"
33
+ },
34
+ "keywords": [
35
+ "haptics",
36
+ "react",
37
+ "hooks",
38
+ "vibration",
39
+ "haptic-feedback"
40
+ ],
41
+ "license": "MIT",
42
+ "scripts": {
43
+ "build": "tsup",
44
+ "test": "vitest run --passWithNoTests",
45
+ "test:watch": "vitest",
46
+ "typecheck": "tsc --noEmit",
47
+ "clean": "rm -rf dist"
48
+ }
49
+ }