@renegades/react-native-tickle 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/LICENSE +21 -0
- package/README.md +352 -0
- package/Tickle.podspec +29 -0
- package/android/CMakeLists.txt +24 -0
- package/android/build.gradle +126 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/cpp-adapter.cpp +6 -0
- package/android/src/main/java/com/margelo/nitro/tickle/Tickle.kt +71 -0
- package/android/src/main/java/com/margelo/nitro/tickle/TicklePackage.kt +22 -0
- package/ios/Tickle.swift +185 -0
- package/ios/TickleUtils.swift +404 -0
- package/lib/module/Tickle.nitro.js +4 -0
- package/lib/module/Tickle.nitro.js.map +1 -0
- package/lib/module/index.js +254 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/Tickle.nitro.d.ts +63 -0
- package/lib/typescript/src/Tickle.nitro.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +148 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/nitro.json +17 -0
- package/nitrogen/generated/android/c++/JHapticCurve.hpp +87 -0
- package/nitrogen/generated/android/c++/JHapticCurveControlPoint.hpp +61 -0
- package/nitrogen/generated/android/c++/JHapticEvent.hpp +94 -0
- package/nitrogen/generated/android/c++/JHapticEventParameter.hpp +62 -0
- package/nitrogen/generated/android/c++/JHapticEventType.hpp +58 -0
- package/nitrogen/generated/android/c++/JHapticImpactStyle.hpp +67 -0
- package/nitrogen/generated/android/c++/JHapticNotificationType.hpp +61 -0
- package/nitrogen/generated/android/c++/JHapticParameterType.hpp +58 -0
- package/nitrogen/generated/android/c++/JHybridTickleSpec.cpp +162 -0
- package/nitrogen/generated/android/c++/JHybridTickleSpec.hpp +79 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/tickle/HapticCurve.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/tickle/HapticCurveControlPoint.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/tickle/HapticEvent.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/tickle/HapticEventParameter.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/tickle/HapticEventType.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/tickle/HapticImpactStyle.kt +26 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/tickle/HapticNotificationType.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/tickle/HapticParameterType.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/tickle/HybridTickleSpec.kt +109 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/tickle/tickleOnLoad.kt +35 -0
- package/nitrogen/generated/android/tickle+autolinking.cmake +81 -0
- package/nitrogen/generated/android/tickle+autolinking.gradle +27 -0
- package/nitrogen/generated/android/tickleOnLoad.cpp +44 -0
- package/nitrogen/generated/android/tickleOnLoad.hpp +25 -0
- package/nitrogen/generated/ios/Tickle+autolinking.rb +60 -0
- package/nitrogen/generated/ios/Tickle-Swift-Cxx-Bridge.cpp +33 -0
- package/nitrogen/generated/ios/Tickle-Swift-Cxx-Bridge.hpp +139 -0
- package/nitrogen/generated/ios/Tickle-Swift-Cxx-Umbrella.hpp +70 -0
- package/nitrogen/generated/ios/TickleAutolinking.mm +33 -0
- package/nitrogen/generated/ios/TickleAutolinking.swift +25 -0
- package/nitrogen/generated/ios/c++/HybridTickleSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridTickleSpecSwift.hpp +185 -0
- package/nitrogen/generated/ios/swift/HapticCurve.swift +46 -0
- package/nitrogen/generated/ios/swift/HapticCurveControlPoint.swift +35 -0
- package/nitrogen/generated/ios/swift/HapticEvent.swift +57 -0
- package/nitrogen/generated/ios/swift/HapticEventParameter.swift +35 -0
- package/nitrogen/generated/ios/swift/HapticEventType.swift +40 -0
- package/nitrogen/generated/ios/swift/HapticImpactStyle.swift +52 -0
- package/nitrogen/generated/ios/swift/HapticNotificationType.swift +44 -0
- package/nitrogen/generated/ios/swift/HapticParameterType.swift +40 -0
- package/nitrogen/generated/ios/swift/HybridTickleSpec.swift +69 -0
- package/nitrogen/generated/ios/swift/HybridTickleSpec_cxx.swift +282 -0
- package/nitrogen/generated/shared/c++/HapticCurve.hpp +96 -0
- package/nitrogen/generated/shared/c++/HapticCurveControlPoint.hpp +87 -0
- package/nitrogen/generated/shared/c++/HapticEvent.hpp +101 -0
- package/nitrogen/generated/shared/c++/HapticEventParameter.hpp +88 -0
- package/nitrogen/generated/shared/c++/HapticEventType.hpp +76 -0
- package/nitrogen/generated/shared/c++/HapticImpactStyle.hpp +88 -0
- package/nitrogen/generated/shared/c++/HapticNotificationType.hpp +80 -0
- package/nitrogen/generated/shared/c++/HapticParameterType.hpp +76 -0
- package/nitrogen/generated/shared/c++/HybridTickleSpec.cpp +34 -0
- package/nitrogen/generated/shared/c++/HybridTickleSpec.hpp +87 -0
- package/package.json +179 -0
- package/react-native.config.js +8 -0
- package/src/Tickle.nitro.ts +84 -0
- package/src/index.tsx +306 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// HybridTickleSpec.hpp
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#if __has_include(<NitroModules/HybridObject.hpp>)
|
|
11
|
+
#include <NitroModules/HybridObject.hpp>
|
|
12
|
+
#else
|
|
13
|
+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
14
|
+
#endif
|
|
15
|
+
|
|
16
|
+
// Forward declaration of `HapticEvent` to properly resolve imports.
|
|
17
|
+
namespace margelo::nitro::tickle { struct HapticEvent; }
|
|
18
|
+
// Forward declaration of `HapticCurve` to properly resolve imports.
|
|
19
|
+
namespace margelo::nitro::tickle { struct HapticCurve; }
|
|
20
|
+
// Forward declaration of `HapticImpactStyle` to properly resolve imports.
|
|
21
|
+
namespace margelo::nitro::tickle { enum class HapticImpactStyle; }
|
|
22
|
+
// Forward declaration of `HapticNotificationType` to properly resolve imports.
|
|
23
|
+
namespace margelo::nitro::tickle { enum class HapticNotificationType; }
|
|
24
|
+
|
|
25
|
+
#include "HapticEvent.hpp"
|
|
26
|
+
#include <vector>
|
|
27
|
+
#include "HapticCurve.hpp"
|
|
28
|
+
#include <string>
|
|
29
|
+
#include "HapticImpactStyle.hpp"
|
|
30
|
+
#include "HapticNotificationType.hpp"
|
|
31
|
+
|
|
32
|
+
namespace margelo::nitro::tickle {
|
|
33
|
+
|
|
34
|
+
using namespace margelo::nitro;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* An abstract base class for `Tickle`
|
|
38
|
+
* Inherit this class to create instances of `HybridTickleSpec` in C++.
|
|
39
|
+
* You must explicitly call `HybridObject`'s constructor yourself, because it is virtual.
|
|
40
|
+
* @example
|
|
41
|
+
* ```cpp
|
|
42
|
+
* class HybridTickle: public HybridTickleSpec {
|
|
43
|
+
* public:
|
|
44
|
+
* HybridTickle(...): HybridObject(TAG) { ... }
|
|
45
|
+
* // ...
|
|
46
|
+
* };
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
class HybridTickleSpec: public virtual HybridObject {
|
|
50
|
+
public:
|
|
51
|
+
// Constructor
|
|
52
|
+
explicit HybridTickleSpec(): HybridObject(TAG) { }
|
|
53
|
+
|
|
54
|
+
// Destructor
|
|
55
|
+
~HybridTickleSpec() override = default;
|
|
56
|
+
|
|
57
|
+
public:
|
|
58
|
+
// Properties
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
public:
|
|
62
|
+
// Methods
|
|
63
|
+
virtual void startHaptic(const std::vector<HapticEvent>& events, const std::vector<HapticCurve>& curves) = 0;
|
|
64
|
+
virtual void stopAllHaptics() = 0;
|
|
65
|
+
virtual void initializeEngine() = 0;
|
|
66
|
+
virtual void destroyEngine() = 0;
|
|
67
|
+
virtual void createContinuousPlayer(const std::string& playerId, double initialIntensity, double initialSharpness) = 0;
|
|
68
|
+
virtual void startContinuousPlayer(const std::string& playerId) = 0;
|
|
69
|
+
virtual void updateContinuousPlayer(const std::string& playerId, double intensityControl, double sharpnessControl) = 0;
|
|
70
|
+
virtual void stopContinuousPlayer(const std::string& playerId) = 0;
|
|
71
|
+
virtual void destroyContinuousPlayer(const std::string& playerId) = 0;
|
|
72
|
+
virtual void setHapticsEnabled(bool enabled) = 0;
|
|
73
|
+
virtual bool getHapticsEnabled() = 0;
|
|
74
|
+
virtual void triggerImpact(HapticImpactStyle style) = 0;
|
|
75
|
+
virtual void triggerNotification(HapticNotificationType type) = 0;
|
|
76
|
+
virtual void triggerSelection() = 0;
|
|
77
|
+
|
|
78
|
+
protected:
|
|
79
|
+
// Hybrid Setup
|
|
80
|
+
void loadHybridMethods() override;
|
|
81
|
+
|
|
82
|
+
protected:
|
|
83
|
+
// Tag for logging
|
|
84
|
+
static constexpr auto TAG = "Tickle";
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
} // namespace margelo::nitro::tickle
|
package/package.json
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@renegades/react-native-tickle",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Build and fine-tune haptic patterns with real-time previews",
|
|
5
|
+
"main": "./lib/module/index.js",
|
|
6
|
+
"types": "./lib/typescript/src/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"source": "./src/index.tsx",
|
|
10
|
+
"types": "./lib/typescript/src/index.d.ts",
|
|
11
|
+
"default": "./lib/module/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"src",
|
|
17
|
+
"lib",
|
|
18
|
+
"android",
|
|
19
|
+
"ios",
|
|
20
|
+
"cpp",
|
|
21
|
+
"nitrogen",
|
|
22
|
+
"nitro.json",
|
|
23
|
+
"*.podspec",
|
|
24
|
+
"react-native.config.js",
|
|
25
|
+
"!ios/build",
|
|
26
|
+
"!android/build",
|
|
27
|
+
"!android/gradle",
|
|
28
|
+
"!android/gradlew",
|
|
29
|
+
"!android/gradlew.bat",
|
|
30
|
+
"!android/local.properties",
|
|
31
|
+
"!**/__tests__",
|
|
32
|
+
"!**/__fixtures__",
|
|
33
|
+
"!**/__mocks__",
|
|
34
|
+
"!**/.*"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"postinstall": "bash scripts/postinstall.sh",
|
|
38
|
+
"clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
|
|
39
|
+
"prepare": "bob build",
|
|
40
|
+
"nitrogen": "nitrogen",
|
|
41
|
+
"typecheck": "tsc",
|
|
42
|
+
"lint": "eslint \"**/*.{js,ts,tsx}\"",
|
|
43
|
+
"test": "jest",
|
|
44
|
+
"release": "release-it --only-version"
|
|
45
|
+
},
|
|
46
|
+
"keywords": [
|
|
47
|
+
"react-native",
|
|
48
|
+
"ios",
|
|
49
|
+
"android"
|
|
50
|
+
],
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "git+https://github.com/Renegades-Studio/react-native-tickle.git"
|
|
54
|
+
},
|
|
55
|
+
"author": "Renegades <info@renegades.xyz> (https://renegades.xyz)",
|
|
56
|
+
"license": "MIT",
|
|
57
|
+
"bugs": {
|
|
58
|
+
"url": "https://github.com/Renegades-Studio/react-native-tickle/issues"
|
|
59
|
+
},
|
|
60
|
+
"homepage": "https://github.com/Renegades-Studio/react-native-tickle#readme",
|
|
61
|
+
"publishConfig": {
|
|
62
|
+
"registry": "https://registry.npmjs.org/"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@commitlint/config-conventional": "^19.8.1",
|
|
66
|
+
"@eslint/compat": "^1.3.2",
|
|
67
|
+
"@eslint/eslintrc": "^3.3.1",
|
|
68
|
+
"@eslint/js": "^9.35.0",
|
|
69
|
+
"@react-native/babel-preset": "0.83.0",
|
|
70
|
+
"@react-native/eslint-config": "0.83.0",
|
|
71
|
+
"@react-native/eslint-plugin": "^0.83.1",
|
|
72
|
+
"@release-it/conventional-changelog": "^10.0.1",
|
|
73
|
+
"@types/jest": "^29.5.14",
|
|
74
|
+
"@types/react": "~19.1.10",
|
|
75
|
+
"@typescript-eslint/eslint-plugin": "^8.52.0",
|
|
76
|
+
"commitlint": "^19.8.1",
|
|
77
|
+
"del-cli": "^6.0.0",
|
|
78
|
+
"eslint": "^9.35.0",
|
|
79
|
+
"eslint-config-prettier": "^10.1.8",
|
|
80
|
+
"eslint-plugin-eslint-comments": "^3.2.0",
|
|
81
|
+
"eslint-plugin-ft-flow": "^3.0.11",
|
|
82
|
+
"eslint-plugin-jest": "^29.12.1",
|
|
83
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
84
|
+
"eslint-plugin-react": "^7.37.5",
|
|
85
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
86
|
+
"eslint-plugin-react-native": "^5.0.0",
|
|
87
|
+
"jest": "^29.7.0",
|
|
88
|
+
"lefthook": "^2.0.3",
|
|
89
|
+
"nitrogen": "^0.32.1",
|
|
90
|
+
"prettier": "^2.8.8",
|
|
91
|
+
"react": "19.1.0",
|
|
92
|
+
"react-native": "0.81.5",
|
|
93
|
+
"react-native-builder-bob": "^0.40.17",
|
|
94
|
+
"react-native-nitro-modules": "0.32.1",
|
|
95
|
+
"release-it": "^19.0.4",
|
|
96
|
+
"turbo": "^2.5.6",
|
|
97
|
+
"typescript": "^5.9.2"
|
|
98
|
+
},
|
|
99
|
+
"peerDependencies": {
|
|
100
|
+
"react": "*",
|
|
101
|
+
"react-native": "*",
|
|
102
|
+
"react-native-nitro-modules": "^0.32.0"
|
|
103
|
+
},
|
|
104
|
+
"packageManager": "bun@1.1.38",
|
|
105
|
+
"react-native-builder-bob": {
|
|
106
|
+
"source": "src",
|
|
107
|
+
"output": "lib",
|
|
108
|
+
"targets": [
|
|
109
|
+
[
|
|
110
|
+
"custom",
|
|
111
|
+
{
|
|
112
|
+
"script": "nitrogen",
|
|
113
|
+
"clean": "nitrogen/"
|
|
114
|
+
}
|
|
115
|
+
],
|
|
116
|
+
[
|
|
117
|
+
"module",
|
|
118
|
+
{
|
|
119
|
+
"esm": true
|
|
120
|
+
}
|
|
121
|
+
],
|
|
122
|
+
[
|
|
123
|
+
"typescript",
|
|
124
|
+
{
|
|
125
|
+
"project": "tsconfig.build.json"
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
]
|
|
129
|
+
},
|
|
130
|
+
"prettier": {
|
|
131
|
+
"quoteProps": "consistent",
|
|
132
|
+
"singleQuote": true,
|
|
133
|
+
"tabWidth": 2,
|
|
134
|
+
"trailingComma": "es5",
|
|
135
|
+
"useTabs": false
|
|
136
|
+
},
|
|
137
|
+
"jest": {
|
|
138
|
+
"preset": "react-native",
|
|
139
|
+
"modulePathIgnorePatterns": [
|
|
140
|
+
"<rootDir>/example/node_modules",
|
|
141
|
+
"<rootDir>/lib/"
|
|
142
|
+
]
|
|
143
|
+
},
|
|
144
|
+
"commitlint": {
|
|
145
|
+
"extends": [
|
|
146
|
+
"@commitlint/config-conventional"
|
|
147
|
+
]
|
|
148
|
+
},
|
|
149
|
+
"release-it": {
|
|
150
|
+
"git": {
|
|
151
|
+
"commitMessage": "chore: release ${version}",
|
|
152
|
+
"tagName": "v${version}"
|
|
153
|
+
},
|
|
154
|
+
"npm": {
|
|
155
|
+
"publish": true
|
|
156
|
+
},
|
|
157
|
+
"github": {
|
|
158
|
+
"release": true
|
|
159
|
+
},
|
|
160
|
+
"plugins": {
|
|
161
|
+
"@release-it/conventional-changelog": {
|
|
162
|
+
"preset": {
|
|
163
|
+
"name": "angular"
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
"create-react-native-library": {
|
|
169
|
+
"type": "nitro-module",
|
|
170
|
+
"languages": "kotlin-swift",
|
|
171
|
+
"tools": [
|
|
172
|
+
"eslint",
|
|
173
|
+
"jest",
|
|
174
|
+
"lefthook",
|
|
175
|
+
"release-it"
|
|
176
|
+
],
|
|
177
|
+
"version": "0.56.0"
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { HybridObject } from 'react-native-nitro-modules';
|
|
2
|
+
|
|
3
|
+
export type HapticEventType = 'transient' | 'continuous';
|
|
4
|
+
export type HapticParameterType = 'intensity' | 'sharpness';
|
|
5
|
+
|
|
6
|
+
// MARK: - System Haptic Types
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Feedback intensity styles for impact haptics.
|
|
10
|
+
* Maps to UIImpactFeedbackGenerator.FeedbackStyle on iOS.
|
|
11
|
+
*/
|
|
12
|
+
export type HapticImpactStyle = 'rigid' | 'heavy' | 'medium' | 'light' | 'soft';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Notification feedback categories for alert-style haptics.
|
|
16
|
+
* Maps to UINotificationFeedbackGenerator.FeedbackType on iOS.
|
|
17
|
+
*/
|
|
18
|
+
export type HapticNotificationType = 'error' | 'success' | 'warning';
|
|
19
|
+
|
|
20
|
+
export type HapticEventParameter = {
|
|
21
|
+
type: HapticParameterType;
|
|
22
|
+
value: number;
|
|
23
|
+
};
|
|
24
|
+
export type HapticCurveControlPoint = {
|
|
25
|
+
relativeTime: number;
|
|
26
|
+
value: number;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type HapticEvent = {
|
|
30
|
+
type: HapticEventType;
|
|
31
|
+
parameters: HapticEventParameter[];
|
|
32
|
+
relativeTime: number;
|
|
33
|
+
duration?: number; // Optional for transient events
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type HapticCurve = {
|
|
37
|
+
type: HapticParameterType;
|
|
38
|
+
controlPoints: HapticCurveControlPoint[];
|
|
39
|
+
relativeTime: number;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export interface Tickle
|
|
43
|
+
extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
|
|
44
|
+
startHaptic(events: HapticEvent[], curves: HapticCurve[]): void;
|
|
45
|
+
stopAllHaptics(): void;
|
|
46
|
+
initializeEngine(): void;
|
|
47
|
+
destroyEngine(): void;
|
|
48
|
+
|
|
49
|
+
createContinuousPlayer(
|
|
50
|
+
playerId: string,
|
|
51
|
+
initialIntensity: number,
|
|
52
|
+
initialSharpness: number
|
|
53
|
+
): void;
|
|
54
|
+
startContinuousPlayer(playerId: string): void;
|
|
55
|
+
updateContinuousPlayer(
|
|
56
|
+
playerId: string,
|
|
57
|
+
intensityControl: number,
|
|
58
|
+
sharpnessControl: number
|
|
59
|
+
): void;
|
|
60
|
+
stopContinuousPlayer(playerId: string): void;
|
|
61
|
+
destroyContinuousPlayer(playerId: string): void;
|
|
62
|
+
|
|
63
|
+
setHapticsEnabled(enabled: boolean): void;
|
|
64
|
+
getHapticsEnabled(): boolean;
|
|
65
|
+
|
|
66
|
+
// MARK: - System Haptics (Predefined OS-level feedback)
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Triggers an impact haptic with the specified style.
|
|
70
|
+
* Simulates a physical collision feedback.
|
|
71
|
+
*/
|
|
72
|
+
triggerImpact(style: HapticImpactStyle): void;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Triggers a notification-style haptic for alerts and status updates.
|
|
76
|
+
*/
|
|
77
|
+
triggerNotification(type: HapticNotificationType): void;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Triggers a selection change haptic.
|
|
81
|
+
* Best used for picker wheels, toggles, and selection changes.
|
|
82
|
+
*/
|
|
83
|
+
triggerSelection(): void;
|
|
84
|
+
}
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { useEffect, useState, useCallback } from 'react';
|
|
2
|
+
import { AppState } from 'react-native';
|
|
3
|
+
import { NitroModules } from 'react-native-nitro-modules';
|
|
4
|
+
import type {
|
|
5
|
+
Tickle,
|
|
6
|
+
HapticCurve,
|
|
7
|
+
HapticEventParameter,
|
|
8
|
+
HapticParameterType,
|
|
9
|
+
HapticImpactStyle,
|
|
10
|
+
HapticNotificationType,
|
|
11
|
+
} from './Tickle.nitro';
|
|
12
|
+
|
|
13
|
+
export type TransientHapticEvent = {
|
|
14
|
+
type: 'transient';
|
|
15
|
+
parameters: HapticEventParameter[];
|
|
16
|
+
relativeTime: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type ContinuousHapticEvent = {
|
|
20
|
+
type: 'continuous';
|
|
21
|
+
parameters: HapticEventParameter[];
|
|
22
|
+
relativeTime: number;
|
|
23
|
+
duration: number;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type HapticEvent = TransientHapticEvent | ContinuousHapticEvent;
|
|
27
|
+
|
|
28
|
+
const TickleHybridObject = NitroModules.createHybridObject<Tickle>('Tickle');
|
|
29
|
+
|
|
30
|
+
const boxedTickle = NitroModules.box(TickleHybridObject);
|
|
31
|
+
|
|
32
|
+
export function startHaptic(
|
|
33
|
+
events: HapticEvent[],
|
|
34
|
+
curves: HapticCurve[]
|
|
35
|
+
): void {
|
|
36
|
+
'worklet';
|
|
37
|
+
|
|
38
|
+
return boxedTickle.unbox().startHaptic(events, curves);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function stopAllHaptics(): void {
|
|
42
|
+
'worklet';
|
|
43
|
+
|
|
44
|
+
return boxedTickle.unbox().stopAllHaptics();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function initializeEngine(): void {
|
|
48
|
+
'worklet';
|
|
49
|
+
|
|
50
|
+
return boxedTickle.unbox().initializeEngine();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function destroyEngine(): void {
|
|
54
|
+
'worklet';
|
|
55
|
+
|
|
56
|
+
return boxedTickle.unbox().destroyEngine();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function createContinuousPlayer(
|
|
60
|
+
playerId: string,
|
|
61
|
+
initialIntensity: number,
|
|
62
|
+
initialSharpness: number
|
|
63
|
+
): void {
|
|
64
|
+
'worklet';
|
|
65
|
+
|
|
66
|
+
return boxedTickle
|
|
67
|
+
.unbox()
|
|
68
|
+
.createContinuousPlayer(playerId, initialIntensity, initialSharpness);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function startContinuousPlayer(playerId: string): void {
|
|
72
|
+
'worklet';
|
|
73
|
+
|
|
74
|
+
return boxedTickle.unbox().startContinuousPlayer(playerId);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function updateContinuousPlayer(
|
|
78
|
+
playerId: string,
|
|
79
|
+
intensityControl: number,
|
|
80
|
+
sharpnessControl: number
|
|
81
|
+
): void {
|
|
82
|
+
'worklet';
|
|
83
|
+
|
|
84
|
+
return boxedTickle
|
|
85
|
+
.unbox()
|
|
86
|
+
.updateContinuousPlayer(playerId, intensityControl, sharpnessControl);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function stopContinuousPlayer(playerId: string): void {
|
|
90
|
+
'worklet';
|
|
91
|
+
return boxedTickle.unbox().stopContinuousPlayer(playerId);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function destroyContinuousPlayer(playerId: string): void {
|
|
95
|
+
'worklet';
|
|
96
|
+
return boxedTickle.unbox().destroyContinuousPlayer(playerId);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// MARK: - System Haptics (Predefined OS-level feedback)
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Triggers an impact haptic with the specified style.
|
|
103
|
+
* Simulates a physical collision feedback.
|
|
104
|
+
*
|
|
105
|
+
* @param style - The intensity style of the impact ('rigid' | 'heavy' | 'medium' | 'light' | 'soft')
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```ts
|
|
109
|
+
* // Light tap for subtle UI feedback
|
|
110
|
+
* triggerImpact('light');
|
|
111
|
+
*
|
|
112
|
+
* // Heavy impact for significant actions
|
|
113
|
+
* triggerImpact('heavy');
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
export function triggerImpact(style: HapticImpactStyle): void {
|
|
117
|
+
'worklet';
|
|
118
|
+
return boxedTickle.unbox().triggerImpact(style);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Triggers a notification-style haptic for alerts and status updates.
|
|
123
|
+
* Use this for communicating outcomes of actions to the user.
|
|
124
|
+
*
|
|
125
|
+
* @param type - The notification type ('error' | 'success' | 'warning')
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```ts
|
|
129
|
+
* // Success feedback after completing an action
|
|
130
|
+
* triggerNotification('success');
|
|
131
|
+
*
|
|
132
|
+
* // Error feedback when something goes wrong
|
|
133
|
+
* triggerNotification('error');
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export function triggerNotification(type: HapticNotificationType): void {
|
|
137
|
+
'worklet';
|
|
138
|
+
return boxedTickle.unbox().triggerNotification(type);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Triggers a selection change haptic.
|
|
143
|
+
* Best used for picker wheels, toggles, and selection changes.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```ts
|
|
147
|
+
* // When user scrolls through picker options
|
|
148
|
+
* triggerSelection();
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
export function triggerSelection(): void {
|
|
152
|
+
'worklet';
|
|
153
|
+
return boxedTickle.unbox().triggerSelection();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// MARK: - Global Haptics Enable/Disable
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Enable or disable haptics globally. This setting is persisted across app restarts.
|
|
160
|
+
* When disabled, all haptic functions become no-ops (no haptics will play).
|
|
161
|
+
* This does not affect engine initialization/destruction.
|
|
162
|
+
*
|
|
163
|
+
* @param enabled - Whether haptics should be enabled
|
|
164
|
+
*/
|
|
165
|
+
export function setHapticsEnabled(enabled: boolean): void {
|
|
166
|
+
'worklet';
|
|
167
|
+
return boxedTickle.unbox().setHapticsEnabled(enabled);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get the current global haptics enabled state.
|
|
172
|
+
* Defaults to true if not previously set.
|
|
173
|
+
*
|
|
174
|
+
* @returns Whether haptics are currently enabled
|
|
175
|
+
*/
|
|
176
|
+
export function getHapticsEnabled(): boolean {
|
|
177
|
+
'worklet';
|
|
178
|
+
return boxedTickle.unbox().getHapticsEnabled();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Hook to manage the global haptics enabled state.
|
|
183
|
+
* Provides reactive state and a setter function.
|
|
184
|
+
*
|
|
185
|
+
* @returns [isEnabled, setEnabled] - Current state and setter function
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* ```tsx
|
|
189
|
+
* function SettingsScreen() {
|
|
190
|
+
* const [hapticsEnabled, setHapticsEnabled] = useHapticsEnabled();
|
|
191
|
+
*
|
|
192
|
+
* return (
|
|
193
|
+
* <Switch
|
|
194
|
+
* value={hapticsEnabled}
|
|
195
|
+
* onValueChange={setHapticsEnabled}
|
|
196
|
+
* />
|
|
197
|
+
* );
|
|
198
|
+
* }
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
export function useHapticsEnabled(): [boolean, (enabled: boolean) => void] {
|
|
202
|
+
const [enabled, setEnabled] = useState(() => getHapticsEnabled());
|
|
203
|
+
|
|
204
|
+
const setHapticsEnabledState = useCallback((value: boolean) => {
|
|
205
|
+
setHapticsEnabled(value);
|
|
206
|
+
setEnabled(value);
|
|
207
|
+
}, []);
|
|
208
|
+
|
|
209
|
+
return [enabled, setHapticsEnabledState] as const;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Hook to manage a continuous haptic player lifecycle.
|
|
214
|
+
*
|
|
215
|
+
* @param playerId - A unique string key to identify this player (e.g., 'my-palette')
|
|
216
|
+
* @param initialIntensity - Initial intensity value (0.0 to 1.0)
|
|
217
|
+
* @param initialSharpness - Initial sharpness value (0.0 to 1.0)
|
|
218
|
+
*
|
|
219
|
+
* @returns Object with JS-bound methods for convenience:
|
|
220
|
+
* - `start()` - Start the continuous haptic
|
|
221
|
+
* - `stop()` - Stop the continuous haptic
|
|
222
|
+
* - `update(intensity, sharpness)` - Update haptic parameters
|
|
223
|
+
* - `playerId` - The player key (for use in worklets)
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* ```tsx
|
|
227
|
+
* const PLAYER_KEY = 'my-palette';
|
|
228
|
+
*
|
|
229
|
+
* function MyComponent() {
|
|
230
|
+
* const { start, stop, update } = useContinuousPlayer(PLAYER_KEY, 1.0, 0.5);
|
|
231
|
+
*
|
|
232
|
+
* const gesture = Gesture.Pan()
|
|
233
|
+
* .onBegin(() => {
|
|
234
|
+
* start();
|
|
235
|
+
* })
|
|
236
|
+
* .onUpdate(() => {
|
|
237
|
+
* update(0.5, 0.5);
|
|
238
|
+
* })
|
|
239
|
+
* .onEnd(() => {
|
|
240
|
+
* stop();
|
|
241
|
+
* });
|
|
242
|
+
* }
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
export function useContinuousPlayer(
|
|
246
|
+
playerId: string,
|
|
247
|
+
initialIntensity: number = 1.0,
|
|
248
|
+
initialSharpness: number = 0.5
|
|
249
|
+
) {
|
|
250
|
+
useEffect(() => {
|
|
251
|
+
createContinuousPlayer(playerId, initialIntensity, initialSharpness);
|
|
252
|
+
|
|
253
|
+
return () => {
|
|
254
|
+
destroyContinuousPlayer(playerId);
|
|
255
|
+
};
|
|
256
|
+
}, [playerId, initialIntensity, initialSharpness]);
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
start: () => {
|
|
260
|
+
'worklet';
|
|
261
|
+
startContinuousPlayer(playerId);
|
|
262
|
+
},
|
|
263
|
+
stop: () => {
|
|
264
|
+
'worklet';
|
|
265
|
+
stopContinuousPlayer(playerId);
|
|
266
|
+
},
|
|
267
|
+
update: (intensity: number, sharpness: number) => {
|
|
268
|
+
'worklet';
|
|
269
|
+
return updateContinuousPlayer(playerId, intensity, sharpness);
|
|
270
|
+
},
|
|
271
|
+
playerId,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function useHapticEngine() {
|
|
276
|
+
useEffect(() => {
|
|
277
|
+
initializeEngine();
|
|
278
|
+
|
|
279
|
+
const off = AppState.addEventListener('change', (state) => {
|
|
280
|
+
if (state === 'active') {
|
|
281
|
+
initializeEngine();
|
|
282
|
+
} else if (state === 'background') {
|
|
283
|
+
destroyEngine();
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
return () => {
|
|
288
|
+
off.remove();
|
|
289
|
+
};
|
|
290
|
+
}, []);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export function HapticProvider({ children }: { children: React.ReactNode }) {
|
|
294
|
+
useHapticEngine();
|
|
295
|
+
|
|
296
|
+
return <>{children}</>;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export { TickleHybridObject };
|
|
300
|
+
export type {
|
|
301
|
+
HapticCurve,
|
|
302
|
+
HapticEventParameter,
|
|
303
|
+
HapticParameterType,
|
|
304
|
+
HapticImpactStyle,
|
|
305
|
+
HapticNotificationType,
|
|
306
|
+
};
|