diff --git a/example/app/_layout.tsx b/example/app/_layout.tsx
index 9efb0a7..d02e21d 100644
--- a/example/app/_layout.tsx
+++ b/example/app/_layout.tsx
@@ -1,3 +1,14 @@
+/* eslint-disable import/no-duplicates */
+/* eslint-disable prettier/prettier */
+import "react-native-gesture-handler";
import { Slot } from "expo-router";
+import { StyleSheet } from "react-native";
+import { GestureHandlerRootView } from "react-native-gesture-handler";
-export default Slot;
+export default () => (
+
+
+
+);
+
+const styles = StyleSheet.create({ container: { flex: 1 } });
diff --git a/example/app/index.tsx b/example/app/index.tsx
index 5218cbd..c463912 100644
--- a/example/app/index.tsx
+++ b/example/app/index.tsx
@@ -1,30 +1,31 @@
import { LineChart } from "@codeherence/react-native-graph";
-import { useMemo } from "react";
-import { StyleSheet, View } from "react-native";
+import { useCallback, useState } from "react";
+import { Button, StyleSheet, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Banner } from "../components/Banner";
+const generateRandomData = (): [number, number][] => {
+ return Array.from({ length: 100 }, (_, i) => [i, Math.random() * 2000]);
+};
+
export default () => {
const { top, bottom } = useSafeAreaInsets();
+ const [data, setData] = useState<[number, number][]>(generateRandomData());
- const data: [number, number][] = useMemo(() => {
- return Array.from({ length: 20 }, (_, i) => [i, Math.random() * 2000]);
+ const handlePress = useCallback(() => {
+ setData(generateRandomData());
}, []);
return (
- }
- />
+
+
);
};
const styles = StyleSheet.create({
container: { flex: 1 },
- chart: { flex: 1, maxHeight: 200 },
+ chart: { flex: 1 },
});
diff --git a/example/metro.config.js b/example/metro.config.js
index eb187b2..b3364ea 100644
--- a/example/metro.config.js
+++ b/example/metro.config.js
@@ -21,11 +21,17 @@ config.resolver.extraNodeModules = new Proxy(
// npm v7+ will install ../node_modules/react-native because of peerDependencies.
// To prevent the incompatible react-native bewtween ./node_modules/react-native and ../node_modules/react-native,
// excludes the one from the parent folder when bundling.
+
+const IGNORED_LIBS = [
+ "react-native",
+ "@shopify/react-native-skia",
+ "react-native-reanimated",
+ "react-native-gesture-handler",
+];
+
config.resolver.blockList = [
...Array.from(config.resolver.blockList ?? []),
- new RegExp(path.resolve("..", "node_modules", "react-native")),
- new RegExp(path.resolve("..", "node_modules", "@shopify/react-native-skia")),
- new RegExp(path.resolve("..", "node_modules", "react-native-reanimated")),
+ ...IGNORED_LIBS.map((lib) => new RegExp(path.resolve("..", "node_modules", lib))),
];
config.resolver.nodeModulesPaths = [
diff --git a/example/package.json b/example/package.json
index f4e9e44..3b22358 100644
--- a/example/package.json
+++ b/example/package.json
@@ -25,6 +25,7 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"react-native": "0.73.4",
+ "react-native-gesture-handler": "^2.15.0",
"react-native-reanimated": "^3.7.2",
"react-native-safe-area-context": "4.8.2",
"react-native-screens": "~3.29.0",
diff --git a/example/yarn.lock b/example/yarn.lock
index 8b8ea16..12c0e89 100644
--- a/example/yarn.lock
+++ b/example/yarn.lock
@@ -1208,6 +1208,13 @@
"@babel/helper-validator-identifier" "^7.22.20"
to-fast-properties "^2.0.0"
+"@egjs/hammerjs@^2.0.17":
+ version "2.0.17"
+ resolved "https://registry.yarnpkg.com/@egjs/hammerjs/-/hammerjs-2.0.17.tgz#5dc02af75a6a06e4c2db0202cae38c9263895124"
+ integrity sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==
+ dependencies:
+ "@types/hammerjs" "^2.0.36"
+
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
@@ -2523,6 +2530,11 @@
"@types/minimatch" "*"
"@types/node" "*"
+"@types/hammerjs@^2.0.36":
+ version "2.0.45"
+ resolved "https://registry.yarnpkg.com/@types/hammerjs/-/hammerjs-2.0.45.tgz#ffa764bb68a66c08db6efb9c816eb7be850577b1"
+ integrity sha512-qkcUlZmX6c4J8q45taBKTL3p+LbITgyx7qhlPYOdOHZB7B31K0mXbP5YA7i7SgDeEGuI9MnumiKPEMrxg8j3KQ==
+
"@types/html-minifier-terser@^6.0.0":
version "6.1.0"
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35"
@@ -5941,6 +5953,13 @@ hermes-profile-transformer@^0.0.6:
dependencies:
source-map "^0.7.3"
+hoist-non-react-statics@^3.3.0:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
+ integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
+ dependencies:
+ react-is "^16.7.0"
+
hosted-git-info@^3.0.2:
version "3.0.8"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.8.tgz#6e35d4cc87af2c5f816e4cb9ce350ba87a3f370d"
@@ -8574,7 +8593,7 @@ react-helmet-async@^1.3.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
-react-is@^16.13.0, react-is@^16.13.1:
+react-is@^16.13.0, react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -8584,6 +8603,17 @@ react-is@^17.0.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
+react-native-gesture-handler@^2.15.0:
+ version "2.15.0"
+ resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.15.0.tgz#f8e6c0451a7bdf065edb7b9be605480db402baa0"
+ integrity sha512-cmMGW8k86o/xgVTBZZOPohvR5re4Vh65PUxH4HbBBJAYTog4aN4wTVTUlnoky01HuSN8/X4h3tI/K3XLPoDnsg==
+ dependencies:
+ "@egjs/hammerjs" "^2.0.17"
+ hoist-non-react-statics "^3.3.0"
+ invariant "^2.2.4"
+ lodash "^4.17.21"
+ prop-types "^15.7.2"
+
react-native-reanimated@^3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-3.7.2.tgz#688a0002d129a8ddcef919093c45c67de18923f5"
diff --git a/package.json b/package.json
index 18f0d55..4c129aa 100644
--- a/package.json
+++ b/package.json
@@ -75,6 +75,7 @@
"jest": "^28.1.1",
"prettier": "^3.2.5",
"react-native-builder-bob": "^0.20.4",
+ "react-native-gesture-handler": "^2.15.0",
"react-native-reanimated": "3.7.2",
"react-native-safe-area-context": "^4.5.0",
"release-it": "^15.0.0",
@@ -87,6 +88,7 @@
"@shopify/react-native-skia": "0.1.241",
"react": ">=18.0.0",
"react-native": ">=0.71.0",
+ "react-native-gesture-handler": ">=2.0.0",
"react-native-reanimated": ">=2.0.0"
},
"engines": {
diff --git a/src/components/LineChart/AxisLabel.tsx b/src/components/LineChart/AxisLabel.tsx
index fe21b55..a838a1b 100644
--- a/src/components/LineChart/AxisLabel.tsx
+++ b/src/components/LineChart/AxisLabel.tsx
@@ -1,20 +1,66 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { useCallback, useState } from "react";
-import { LayoutChangeEvent } from "react-native";
-import Reanimated from "react-native-reanimated";
+import { LayoutChangeEvent, StyleSheet, View } from "react-native";
+import Reanimated, {
+ clamp,
+ useAnimatedStyle,
+ useDerivedValue,
+ withTiming,
+} from "react-native-reanimated";
+
+export interface AxisLabelComponentProps {
+ value: number;
+}
interface AxisLabelContainerProps {
children: React.ReactNode;
+ x: number;
+ containerWidth: number;
}
-export const AxisLabelContainer: React.FC = ({ children }) => {
- const [_, setWidth] = useState(0);
- const [__, setHeight] = useState(0);
+export const AxisLabelContainer: React.FC = ({
+ x,
+ containerWidth,
+ children,
+}) => {
+ const [width, setWidth] = useState(0);
const onLayout = useCallback((event: LayoutChangeEvent) => {
setWidth(event.nativeEvent.layout.width);
- setHeight(event.nativeEvent.layout.height);
}, []);
- return {children};
+ const translateX = useDerivedValue(() => {
+ const halfWidth = Math.round(width / 2);
+ const minX = 0;
+ const maxX = containerWidth - width;
+
+ return withTiming(clamp(x - halfWidth, minX, maxX));
+ }, [x, containerWidth, width]);
+
+ const transform = useAnimatedStyle(() => {
+ return {
+ transform: [{ translateX: translateX.value }],
+ };
+ });
+
+ return (
+
+
+ {children}
+
+
+ );
};
+
+const styles = StyleSheet.create({
+ container: {
+ position: "relative",
+ flexDirection: "row",
+ width: "100%",
+ overflow: "hidden",
+ },
+ child: {
+ justifyContent: "center",
+ alignItems: "center",
+ },
+});
diff --git a/src/components/LineChart/Math.ts b/src/components/LineChart/Math.ts
index 1531402..65ef8bf 100644
--- a/src/components/LineChart/Math.ts
+++ b/src/components/LineChart/Math.ts
@@ -1,155 +1,27 @@
-import type { Vector, PathCommand, SkPath } from "@shopify/react-native-skia";
+import type { Vector, SkPath } from "@shopify/react-native-skia";
import { PathVerb, Skia, vec } from "@shopify/react-native-skia";
import { scaleSqrt, scaleTime } from "d3";
import { CurveFactory, curveLinear, line } from "d3-shape";
-// Source:
-// code from William Candillon
+interface RoundProps {
+ value: number;
+ precision?: number;
+}
-const round = (value: number, precision = 0): number => {
+const round = ({ value, precision = 0 }: RoundProps): number => {
"worklet";
const p = Math.pow(10, precision);
return Math.round(value * p) / p;
};
-// https://stackoverflow.com/questions/27176423/function-to-solve-cubic-equation-analytically
-const cuberoot = (x: number): number => {
- "worklet";
-
- const y = Math.pow(Math.abs(x), 1 / 3);
- return x < 0 ? -y : y;
-};
-
-const solveCubic = (a: number, b: number, c: number, d: number): number[] => {
- "worklet";
-
- if (Math.abs(a) < 1e-8) {
- // Quadratic case, ax^2+bx+c=0
- a = b;
- b = c;
- c = d;
- if (Math.abs(a) < 1e-8) {
- // Linear case, ax+b=0
- a = b;
- b = c;
- if (Math.abs(a) < 1e-8) {
- // Degenerate case
- return [];
- }
- return [-b / a];
- }
-
- const D = b * b - 4 * a * c;
- if (Math.abs(D) < 1e-8) return [-b / (2 * a)];
- if (D > 0) return [(-b + Math.sqrt(D)) / (2 * a), (-b - Math.sqrt(D)) / (2 * a)];
-
- return [];
- }
-
- // Convert to depressed cubic t^3+pt+q = 0 (subst x = t - b/3a)
- const p = (3 * a * c - b * b) / (3 * a * a);
- const q = (2 * b * b * b - 9 * a * b * c + 27 * a * a * d) / (27 * a * a * a);
- let roots;
-
- if (Math.abs(p) < 1e-8) {
- // p = 0 -> t^3 = -q -> t = -q^1/3
- roots = [cuberoot(-q)];
- } else if (Math.abs(q) < 1e-8) {
- // q = 0 -> t^3 + pt = 0 -> t(t^2+p)=0
- roots = [0].concat(p < 0 ? [Math.sqrt(-p), -Math.sqrt(-p)] : []);
- } else {
- const D = (q * q) / 4 + (p * p * p) / 27;
- if (Math.abs(D) < 1e-8) {
- // D = 0 -> two roots
- roots = [(-1.5 * q) / p, (3 * q) / p];
- } else if (D > 0) {
- // Only one real root
- const u = cuberoot(-q / 2 - Math.sqrt(D));
- roots = [u - p / (3 * u)];
- } else {
- // D < 0, three roots, but needs to use complex numbers/trigonometric solution
- const u = 2 * Math.sqrt(-p / 3);
- const t = Math.acos((3 * q) / p / u) / 3; // D < 0 implies p < 0 and acos argument in [-1..1]
- const k = (2 * Math.PI) / 3;
- roots = [u * Math.cos(t), u * Math.cos(t - k), u * Math.cos(t - 2 * k)];
- }
- }
-
- // Convert back from depressed cubic
- for (let i = 0; i < roots.length; i++) roots[i] -= b / (3 * a);
-
- return roots;
-};
-
-const cubicBezier = (t: number, from: number, c1: number, c2: number, to: number): number => {
- "worklet";
-
- const term = 1 - t;
- const a = 1 * term ** 3 * t ** 0 * from;
- const b = 3 * term ** 2 * t ** 1 * c1;
- const c = 3 * term ** 1 * t ** 2 * c2;
- const d = 1 * term ** 0 * t ** 3 * to;
- return a + b + c + d;
-};
-
-export const cubicBezierYForX = (
- x: number,
- a: Vector,
- b: Vector,
- c: Vector,
- d: Vector,
- precision = 2
-): number => {
- "worklet";
-
- const pa = -a.x + 3 * b.x - 3 * c.x + d.x;
- const pb = 3 * a.x - 6 * b.x + 3 * c.x;
- const pc = -3 * a.x + 3 * b.x;
- const pd = a.x - x;
- const ts = solveCubic(pa, pb, pc, pd)
- .map((root) => round(root, precision))
- .filter((root) => root >= 0 && root <= 1);
- const t = ts[0];
- if (t == null) return 0;
- return cubicBezier(t, a.y, b.y, c.y, d.y);
-};
-
-interface Cubic {
- from: Vector;
- c1: Vector;
- c2: Vector;
- to: Vector;
+interface LinearYForXProps {
+ path: SkPath;
+ x: number;
+ precision?: number;
}
-export const selectCurve = (cmds: PathCommand[], x: number): Cubic | undefined => {
- "worklet";
-
- let from: Vector = vec(0, 0);
- for (let i = 0; i < cmds.length; i++) {
- const cmd = cmds[i];
- if (cmd == null) return undefined;
- if (cmd[0] === PathVerb.Move) {
- from = vec(cmd[1], cmd[2]);
- } else if (cmd[0] === PathVerb.Cubic) {
- const c1 = vec(cmd[1], cmd[2]);
- const c2 = vec(cmd[3], cmd[4]);
- const to = vec(cmd[5], cmd[6]);
- if (x >= from.x && x <= to.x) {
- return {
- from,
- c1,
- c2,
- to,
- };
- }
- from = to;
- }
- }
- return undefined;
-};
-
-const linearYForX = (path: SkPath, x: number, precision = 2): number => {
+const linearYForX = ({ path, x, precision = 2 }: LinearYForXProps): number => {
"worklet";
const cmds = path.toCmds();
@@ -164,7 +36,7 @@ const linearYForX = (path: SkPath, x: number, precision = 2): number => {
// If the x is between the two points, return the rounded interpolation of the y value
if (x >= from.x && x <= to.x) {
const t = (x - from.x) / (to.x - from.x);
- return round(from.y + t * (to.y - from.y), precision);
+ return round({ value: from.y + t * (to.y - from.y), precision });
}
from = to;
}
@@ -181,7 +53,7 @@ export interface GetYForXProps {
export const getYForX = ({ path, x, precision = 2 }: GetYForXProps): number | undefined => {
"worklet";
- return linearYForX(path, x, precision);
+ return linearYForX({ path, x, precision });
};
export interface ComputePathProps {
@@ -230,3 +102,80 @@ export const computePath = ({
if (rawPath === null) return straightLine;
return Skia.Path.MakeFromSVGString(rawPath) ?? straightLine;
};
+
+interface GetMinValueProps {
+ points: [number, number][];
+}
+
+/**
+ * Get the index and value of the minimum value in the points array
+ * @returns The index and value of the minimum value in the points array
+ */
+const getMinValue = ({ points }: GetMinValueProps): [number, number] => {
+ if (points.length === 0) return [0, 0];
+
+ return points.reduce<[number, number]>(
+ (acc, [_, value], index) => {
+ if (value < acc[1]) return [index, value];
+ return acc;
+ },
+ [0, Number.MAX_SAFE_INTEGER]
+ );
+};
+
+interface GetMaxValueProps {
+ points: [number, number][];
+}
+
+/**
+ * Get the index and value of the maximum value in the points array
+ * @returns The index and value of the maximum value in the points array
+ */
+const getMaxValue = ({ points }: GetMaxValueProps): [number, number] => {
+ if (points.length === 0) return [0, 0];
+
+ return points.reduce<[number, number]>(
+ (acc, [_, value], index) => {
+ if (value > acc[1]) return [index, value];
+ return acc;
+ },
+ [0, Number.MIN_SAFE_INTEGER]
+ );
+};
+
+export const computeGraphData = (points: [number, number][]) => {
+ if (points.length === 0) {
+ return {
+ points: [],
+ minTimestamp: 0,
+ maxTimestamp: 0,
+ minValue: 0,
+ minValueIndex: 0,
+ minValueXProportion: 0,
+ maxValue: 0,
+ maxValueIndex: 0,
+ maxValueXProportion: 0,
+ };
+ }
+
+ const timestamps = points.map(([timestamp]) => timestamp);
+ const minTimestamp = Math.min(...timestamps);
+ const maxTimestamp = Math.max(...timestamps);
+ const [minValueIndex, minValue] = getMinValue({ points });
+ const [maxValueIndex, maxValue] = getMaxValue({ points });
+
+ const minValueXProportion = minValueIndex / points.length;
+ const maxValueXProportion = maxValueIndex / points.length;
+
+ return {
+ points,
+ minTimestamp,
+ maxTimestamp,
+ minValue,
+ minValueIndex,
+ minValueXProportion,
+ maxValue,
+ maxValueIndex,
+ maxValueXProportion,
+ };
+};
diff --git a/src/components/LineChart/index.tsx b/src/components/LineChart/index.tsx
index 30ca478..46c861f 100644
--- a/src/components/LineChart/index.tsx
+++ b/src/components/LineChart/index.tsx
@@ -1,18 +1,19 @@
import { Canvas, Path } from "@shopify/react-native-skia";
import { useCallback, useMemo, useState } from "react";
-import { LayoutChangeEvent, StyleSheet, View, ViewProps } from "react-native";
+import { LayoutChangeEvent, Platform, StyleSheet, Text, View, ViewProps } from "react-native";
+import { Gesture, GestureDetector } from "react-native-gesture-handler";
import { useDerivedValue, useSharedValue } from "react-native-reanimated";
+import { AxisLabelContainer } from "./AxisLabel";
import type { BannerComponentProps } from "./Banner";
import { Cursor } from "./Cursor";
-import { computePath, getYForX, type ComputePathProps } from "./Math";
+import { computePath, getYForX, type ComputePathProps, computeGraphData } from "./Math";
import {
DEFAULT_CURSOR_RADIUS,
DEFAULT_CURVE_TYPE,
DEFAULT_FORMATTER,
DEFAULT_STROKE_WIDTH,
} from "./constants";
-import { useGestureHandler } from "./useGestureHandler";
export type LineChartProps = ViewProps & {
/** Array of [x, y] points for the chart */
@@ -27,8 +28,15 @@ export type LineChartProps = ViewProps & {
BottomAxisLabel?: React.FC;
};
+const currencyFormatter = new Intl.NumberFormat("en-US", {
+ style: "currency",
+ currency: "USD",
+});
+
+const GestureMethod = Platform.OS === "web" ? Gesture.Hover : Gesture.Pan;
+
export const LineChart: React.FC = ({
- points: _points,
+ points,
strokeWidth = DEFAULT_STROKE_WIDTH,
cursorRadius = DEFAULT_CURSOR_RADIUS,
curveType = DEFAULT_CURVE_TYPE,
@@ -43,24 +51,20 @@ export const LineChart: React.FC = ({
// Initially -cursorRadius so that the cursor is offscreen
const x = useSharedValue(-cursorRadius);
- const onTouch = useGestureHandler({ x, cursorRadius });
+ const gesture = GestureMethod()
+ // Follow the cursor on the x-axis
+ .onBegin((evt) => (x.value = evt.x))
+ .onChange((evt) => (x.value = evt.x))
+ // When the gesture ends, we reset the x value to -cursorRadius so that the cursor is offscreen
+ .onEnd(() => (x.value = -cursorRadius));
// We separate the computation of the data from the rendering. This is so that these values are
// not recomputed when the width or height of the chart changes, but only when the points change.
- const computedData = useMemo(() => {
- const timestamps = _points.map(([timestamp]) => timestamp);
- const values = _points.map(([, value]) => value);
- const minTimestamp = Math.min(...timestamps);
- const maxTimestamp = Math.max(...timestamps);
- const minValue = Math.min(...values);
- const maxValue = Math.max(...values);
-
- return { points: _points, minTimestamp, maxTimestamp, minValue, maxValue };
- }, [_points]);
+ const data = useMemo(() => computeGraphData(points), [points]);
const path = useMemo(() => {
- return computePath({ ...computedData, width, height, cursorRadius, curveType });
- }, [computedData, width, height]);
+ return computePath({ ...data, width, height, cursorRadius, curveType });
+ }, [data, width, height]);
const y = useDerivedValue(() => {
return path ? getYForX({ path, x: x.value }) ?? 0 : 0;
@@ -72,18 +76,27 @@ export const LineChart: React.FC = ({
}, []);
return (
-
-
-
-
+
+
+ {currencyFormatter.format(data.maxValue)}
+
+
+
+
+
+
+
+ {currencyFormatter.format(data.minValue)}
+
);
};
const styles = StyleSheet.create({
+ root: { position: "relative" },
container: { flex: 1 },
canvas: { flex: 1 },
});
diff --git a/src/components/LineChart/useGestureHandler.ts b/src/components/LineChart/useGestureHandler.ts
deleted file mode 100644
index 863eb54..0000000
--- a/src/components/LineChart/useGestureHandler.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { useTouchHandler } from "@shopify/react-native-skia";
-import type { SharedValueType } from "@shopify/react-native-skia";
-
-interface UseGestureHandlerProps {
- x: SharedValueType;
- cursorRadius: number;
-}
-
-export const useGestureHandler = ({ x, cursorRadius }: UseGestureHandlerProps) => {
- return useTouchHandler(
- {
- onStart: (ti) => {
- x.value = ti.x;
- },
- onActive: (ti) => {
- x.value = ti.x;
- },
- onEnd: () => {
- // Hide the cursor when the touch ends
- x.value = -cursorRadius;
- },
- },
- [x]
- );
-};
diff --git a/yarn.lock b/yarn.lock
index ea9e4ce..3ef3ac1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1246,6 +1246,13 @@
dependencies:
"@jridgewell/trace-mapping" "0.3.9"
+"@egjs/hammerjs@^2.0.17":
+ version "2.0.17"
+ resolved "https://registry.yarnpkg.com/@egjs/hammerjs/-/hammerjs-2.0.17.tgz#5dc02af75a6a06e4c2db0202cae38c9263895124"
+ integrity sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==
+ dependencies:
+ "@types/hammerjs" "^2.0.36"
+
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
@@ -2043,6 +2050,11 @@
dependencies:
"@types/node" "*"
+"@types/hammerjs@^2.0.36":
+ version "2.0.45"
+ resolved "https://registry.yarnpkg.com/@types/hammerjs/-/hammerjs-2.0.45.tgz#ffa764bb68a66c08db6efb9c816eb7be850577b1"
+ integrity sha512-qkcUlZmX6c4J8q45taBKTL3p+LbITgyx7qhlPYOdOHZB7B31K0mXbP5YA7i7SgDeEGuI9MnumiKPEMrxg8j3KQ==
+
"@types/http-cache-semantics@^4.0.2":
version "4.0.4"
resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4"
@@ -4826,6 +4838,13 @@ hasown@^2.0.0, hasown@^2.0.1:
dependencies:
function-bind "^1.1.2"
+hoist-non-react-statics@^3.3.0:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
+ integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
+ dependencies:
+ react-is "^16.7.0"
+
hosted-git-info@^2.1.4:
version "2.8.9"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
@@ -6895,7 +6914,7 @@ prompts@^2.0.1, prompts@^2.4.2:
kleur "^3.0.3"
sisteransi "^1.0.5"
-prop-types@^15.8.1:
+prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -6983,7 +7002,7 @@ rc@1.2.8:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
-react-is@^16.13.1:
+react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -7020,6 +7039,17 @@ react-native-builder-bob@^0.20.4:
optionalDependencies:
jetifier "^2.0.0"
+react-native-gesture-handler@^2.15.0:
+ version "2.15.0"
+ resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.15.0.tgz#f8e6c0451a7bdf065edb7b9be605480db402baa0"
+ integrity sha512-cmMGW8k86o/xgVTBZZOPohvR5re4Vh65PUxH4HbBBJAYTog4aN4wTVTUlnoky01HuSN8/X4h3tI/K3XLPoDnsg==
+ dependencies:
+ "@egjs/hammerjs" "^2.0.17"
+ hoist-non-react-statics "^3.3.0"
+ invariant "^2.2.4"
+ lodash "^4.17.21"
+ prop-types "^15.7.2"
+
react-native-reanimated@3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-3.7.2.tgz#688a0002d129a8ddcef919093c45c67de18923f5"