@obitrain/charts 0.10.3 → 0.11.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.
Files changed (93) hide show
  1. package/README.md +4 -2
  2. package/lib/commonjs/Charts/BottomAxis/BottomAxis.js +53 -13
  3. package/lib/commonjs/Charts/BottomAxis/BottomAxis.js.map +1 -1
  4. package/lib/commonjs/Charts/BottomAxis/Tick.js +27 -11
  5. package/lib/commonjs/Charts/BottomAxis/Tick.js.map +1 -1
  6. package/lib/commonjs/Charts/BottomAxis/index.js.map +1 -1
  7. package/lib/commonjs/Charts/Cursor.js +18 -6
  8. package/lib/commonjs/Charts/Cursor.js.map +1 -1
  9. package/lib/commonjs/Charts/Dot.js +51 -0
  10. package/lib/commonjs/Charts/Dot.js.map +1 -0
  11. package/lib/commonjs/Charts/Dots.js +74 -0
  12. package/lib/commonjs/Charts/Dots.js.map +1 -0
  13. package/lib/commonjs/Charts/YAxis.js +64 -0
  14. package/lib/commonjs/Charts/YAxis.js.map +1 -0
  15. package/lib/commonjs/Charts/gesture.js +67 -24
  16. package/lib/commonjs/Charts/gesture.js.map +1 -1
  17. package/lib/commonjs/Charts/graphUtils.js +4 -1
  18. package/lib/commonjs/Charts/graphUtils.js.map +1 -1
  19. package/lib/commonjs/Charts/index.js +33 -0
  20. package/lib/commonjs/Charts/index.js.map +1 -1
  21. package/lib/module/Charts/BottomAxis/BottomAxis.js +55 -14
  22. package/lib/module/Charts/BottomAxis/BottomAxis.js.map +1 -1
  23. package/lib/module/Charts/BottomAxis/Tick.js +28 -10
  24. package/lib/module/Charts/BottomAxis/Tick.js.map +1 -1
  25. package/lib/module/Charts/BottomAxis/index.js.map +1 -1
  26. package/lib/module/Charts/Cursor.js +19 -7
  27. package/lib/module/Charts/Cursor.js.map +1 -1
  28. package/lib/module/Charts/Dot.js +47 -0
  29. package/lib/module/Charts/Dot.js.map +1 -0
  30. package/lib/module/Charts/Dots.js +70 -0
  31. package/lib/module/Charts/Dots.js.map +1 -0
  32. package/lib/module/Charts/YAxis.js +59 -0
  33. package/lib/module/Charts/YAxis.js.map +1 -0
  34. package/lib/module/Charts/gesture.js +67 -25
  35. package/lib/module/Charts/gesture.js.map +1 -1
  36. package/lib/module/Charts/graphUtils.js +4 -1
  37. package/lib/module/Charts/graphUtils.js.map +1 -1
  38. package/lib/module/Charts/index.js +4 -1
  39. package/lib/module/Charts/index.js.map +1 -1
  40. package/lib/typescript/commonjs/src/Charts/BottomAxis/BottomAxis.d.ts +26 -3
  41. package/lib/typescript/commonjs/src/Charts/BottomAxis/BottomAxis.d.ts.map +1 -1
  42. package/lib/typescript/commonjs/src/Charts/BottomAxis/Tick.d.ts +16 -3
  43. package/lib/typescript/commonjs/src/Charts/BottomAxis/Tick.d.ts.map +1 -1
  44. package/lib/typescript/commonjs/src/Charts/BottomAxis/index.d.ts +1 -1
  45. package/lib/typescript/commonjs/src/Charts/BottomAxis/index.d.ts.map +1 -1
  46. package/lib/typescript/commonjs/src/Charts/Cursor.d.ts +3 -0
  47. package/lib/typescript/commonjs/src/Charts/Cursor.d.ts.map +1 -1
  48. package/lib/typescript/commonjs/src/Charts/Dot.d.ts +22 -0
  49. package/lib/typescript/commonjs/src/Charts/Dot.d.ts.map +1 -0
  50. package/lib/typescript/commonjs/src/Charts/Dots.d.ts +28 -0
  51. package/lib/typescript/commonjs/src/Charts/Dots.d.ts.map +1 -0
  52. package/lib/typescript/commonjs/src/Charts/YAxis.d.ts +22 -0
  53. package/lib/typescript/commonjs/src/Charts/YAxis.d.ts.map +1 -0
  54. package/lib/typescript/commonjs/src/Charts/ZoomableLinechart/ScalablePath.d.ts +1 -1
  55. package/lib/typescript/commonjs/src/Charts/ZoomableLinechart/ScalablePath.d.ts.map +1 -1
  56. package/lib/typescript/commonjs/src/Charts/gesture.d.ts +6 -0
  57. package/lib/typescript/commonjs/src/Charts/gesture.d.ts.map +1 -1
  58. package/lib/typescript/commonjs/src/Charts/graphUtils.d.ts.map +1 -1
  59. package/lib/typescript/commonjs/src/Charts/index.d.ts +8 -2
  60. package/lib/typescript/commonjs/src/Charts/index.d.ts.map +1 -1
  61. package/lib/typescript/module/src/Charts/BottomAxis/BottomAxis.d.ts +26 -3
  62. package/lib/typescript/module/src/Charts/BottomAxis/BottomAxis.d.ts.map +1 -1
  63. package/lib/typescript/module/src/Charts/BottomAxis/Tick.d.ts +16 -3
  64. package/lib/typescript/module/src/Charts/BottomAxis/Tick.d.ts.map +1 -1
  65. package/lib/typescript/module/src/Charts/BottomAxis/index.d.ts +1 -1
  66. package/lib/typescript/module/src/Charts/BottomAxis/index.d.ts.map +1 -1
  67. package/lib/typescript/module/src/Charts/Cursor.d.ts +3 -0
  68. package/lib/typescript/module/src/Charts/Cursor.d.ts.map +1 -1
  69. package/lib/typescript/module/src/Charts/Dot.d.ts +22 -0
  70. package/lib/typescript/module/src/Charts/Dot.d.ts.map +1 -0
  71. package/lib/typescript/module/src/Charts/Dots.d.ts +28 -0
  72. package/lib/typescript/module/src/Charts/Dots.d.ts.map +1 -0
  73. package/lib/typescript/module/src/Charts/YAxis.d.ts +22 -0
  74. package/lib/typescript/module/src/Charts/YAxis.d.ts.map +1 -0
  75. package/lib/typescript/module/src/Charts/ZoomableLinechart/ScalablePath.d.ts +1 -1
  76. package/lib/typescript/module/src/Charts/ZoomableLinechart/ScalablePath.d.ts.map +1 -1
  77. package/lib/typescript/module/src/Charts/gesture.d.ts +6 -0
  78. package/lib/typescript/module/src/Charts/gesture.d.ts.map +1 -1
  79. package/lib/typescript/module/src/Charts/graphUtils.d.ts.map +1 -1
  80. package/lib/typescript/module/src/Charts/index.d.ts +8 -2
  81. package/lib/typescript/module/src/Charts/index.d.ts.map +1 -1
  82. package/package.json +4 -2
  83. package/src/Charts/BottomAxis/BottomAxis.tsx +100 -19
  84. package/src/Charts/BottomAxis/Tick.tsx +55 -11
  85. package/src/Charts/BottomAxis/index.tsx +5 -1
  86. package/src/Charts/Cursor.tsx +21 -1
  87. package/src/Charts/Dot.tsx +64 -0
  88. package/src/Charts/Dots.tsx +96 -0
  89. package/src/Charts/YAxis.tsx +96 -0
  90. package/src/Charts/ZoomableLinechart/ScalablePath.tsx +1 -1
  91. package/src/Charts/gesture.ts +109 -51
  92. package/src/Charts/graphUtils.ts +4 -1
  93. package/src/Charts/index.tsx +8 -0
@@ -0,0 +1,96 @@
1
+ import { Path, Skia, type Color } from '@shopify/react-native-skia';
2
+ import { type FC } from 'react';
3
+ import { useDerivedValue, type SharedValue } from 'react-native-reanimated';
4
+ import { getPositionWl } from './gesture';
5
+ import type { AnimatedDot } from './types';
6
+
7
+ export type DotShape = 'circle' | 'diamond';
8
+
9
+ export type DotsProps = {
10
+ dots: AnimatedDot[];
11
+ r?: number;
12
+ shape?: DotShape;
13
+ color?: Color;
14
+ /** When set, dots are drawn as rings: `fillColor` inside, `color` as the stroke. */
15
+ fillColor?: Color;
16
+ strokeWidth?: number;
17
+ /** Viewport width used to cull off-screen dots. */
18
+ width?: number;
19
+ // Optional zoom/pan awareness (see useScalableGesture)
20
+ scale?: SharedValue<number>;
21
+ focalX?: SharedValue<number>;
22
+ offsetX?: SharedValue<number>;
23
+ };
24
+
25
+ /**
26
+ * Draw a whole series of dots as a single Skia path (two with `fillColor`).
27
+ * Unlike mapping over {@link Dot}, this runs one worklet per frame instead of
28
+ * one per dot, which keeps large series smooth during zoom/pan gestures.
29
+ * Per-dot opacity is binary here: dots below 0.5 are hidden.
30
+ */
31
+ const Dots: FC<DotsProps> = function (props) {
32
+ const {
33
+ dots,
34
+ r = 4,
35
+ shape = 'circle',
36
+ color = 'black',
37
+ fillColor,
38
+ strokeWidth = 2,
39
+ width,
40
+ scale,
41
+ focalX,
42
+ offsetX,
43
+ } = props;
44
+
45
+ const path = useDerivedValue(() => {
46
+ const p = Skia.Path.Make();
47
+ const margin = r + strokeWidth;
48
+ for (const dot of dots) {
49
+ if (dot.opacity.value < 0.5) continue;
50
+ const x =
51
+ scale === undefined || focalX === undefined || offsetX === undefined
52
+ ? dot.x.value
53
+ : getPositionWl(
54
+ dot.x.value,
55
+ focalX.value,
56
+ scale.value,
57
+ offsetX.value
58
+ );
59
+ if (width !== undefined && (x < -margin || x > width + margin)) continue;
60
+ const y = dot.y.value;
61
+ if (shape === 'diamond') {
62
+ // One addPoly beats four moveTo/lineTo calls: each is a JSI hop, and
63
+ // this worklet rebuilds every dot on every frame of a gesture
64
+ p.addPoly(
65
+ [
66
+ { x, y: y - r },
67
+ { x: x + r, y },
68
+ { x, y: y + r },
69
+ { x: x - r, y },
70
+ ],
71
+ true
72
+ );
73
+ } else {
74
+ p.addCircle(x, y, r);
75
+ }
76
+ }
77
+ return p;
78
+ });
79
+
80
+ return (
81
+ <>
82
+ <Path path={path} color={fillColor ?? color} />
83
+ {fillColor !== undefined ? (
84
+ <Path
85
+ path={path}
86
+ style="stroke"
87
+ color={color}
88
+ strokeWidth={strokeWidth}
89
+ strokeJoin="round"
90
+ />
91
+ ) : null}
92
+ </>
93
+ );
94
+ };
95
+
96
+ export { Dots };
@@ -0,0 +1,96 @@
1
+ import {
2
+ Group,
3
+ Line,
4
+ Text,
5
+ vec,
6
+ type Color,
7
+ type SkFont,
8
+ } from '@shopify/react-native-skia';
9
+ import { type FC } from 'react';
10
+ import { StyleSheet } from 'react-native';
11
+
12
+ export type YAxisProps = {
13
+ width: number;
14
+ height: number;
15
+ font: SkFont;
16
+ minY: number;
17
+ maxY: number;
18
+ /** Values to draw a gridline at. Defaults to `nbTicks` evenly spaced values. */
19
+ values?: number[];
20
+ nbTicks?: number;
21
+ color?: Color;
22
+ labelColor?: Color;
23
+ strokeWidth?: number;
24
+ showLabels?: boolean;
25
+ formatLabel?: (value: number) => string;
26
+ };
27
+
28
+ const defaultFormatLabel = (value: number) => `${Math.round(value)}`;
29
+
30
+ /** Evenly spaced integer ticks from 0 to a padded maximum, e.g. 0, 26, 52, 78, 104. */
31
+ export const getPaddedTicks = function (
32
+ maxValue: number,
33
+ nbTicks = 4,
34
+ padding = 1.15
35
+ ): number[] {
36
+ const step = Math.max(1, Math.ceil((maxValue * padding) / nbTicks));
37
+ return Array.from({ length: nbTicks + 1 }, (_, i) => i * step);
38
+ };
39
+
40
+ const YAxis: FC<YAxisProps> = function (props) {
41
+ const {
42
+ width,
43
+ height,
44
+ font,
45
+ minY,
46
+ maxY,
47
+ nbTicks = 4,
48
+ color = 'black',
49
+ labelColor,
50
+ strokeWidth = StyleSheet.hairlineWidth,
51
+ showLabels = true,
52
+ formatLabel = defaultFormatLabel,
53
+ } = props;
54
+
55
+ const values =
56
+ props.values ??
57
+ Array.from(
58
+ { length: nbTicks },
59
+ (_, i) => minY + ((i + 1) * (maxY - minY)) / nbTicks
60
+ );
61
+
62
+ return (
63
+ <Group>
64
+ {values.map((value, i) => {
65
+ const y = height - ((value - minY) * height) / (maxY - minY);
66
+ const label = formatLabel(value);
67
+ const labelWidth = font
68
+ .getGlyphWidths(font.getGlyphIDs(label))
69
+ .reduce((a, b) => a + b, 0);
70
+ const lineWidth = showLabels ? width - 10 - labelWidth : width;
71
+
72
+ return (
73
+ <Group key={`YTick-${i}`} transform={[{ translateY: y }]}>
74
+ <Line
75
+ p1={vec(0, 0)}
76
+ p2={vec(lineWidth, 0)}
77
+ color={color}
78
+ strokeWidth={strokeWidth}
79
+ />
80
+ {showLabels ? (
81
+ <Text
82
+ text={label}
83
+ x={width - labelWidth}
84
+ y={font.getSize() / 2.5}
85
+ font={font}
86
+ color={labelColor ?? color}
87
+ />
88
+ ) : null}
89
+ </Group>
90
+ );
91
+ })}
92
+ </Group>
93
+ );
94
+ };
95
+
96
+ export { YAxis };
@@ -8,7 +8,7 @@ export type ScalablePathProps = {
8
8
  focalX: SharedValue<number>;
9
9
  offsetX: SharedValue<number>;
10
10
  color?: string;
11
- pathProps?: Omit<React.ComponentProps<typeof Path>, 'color'>;
11
+ pathProps?: Omit<React.ComponentProps<typeof Path>, 'color' | 'path'>;
12
12
  };
13
13
 
14
14
  const ScalablePath: FC<ScalablePathProps> = function (props) {
@@ -1,10 +1,9 @@
1
1
  import { clamp } from '@shopify/react-native-skia';
2
- import { useCallback } from 'react';
2
+ import { useCallback, useMemo, useRef } from 'react';
3
3
  import { Gesture, type PanGesture, type PinchGesture } from 'react-native-gesture-handler';
4
4
  import {
5
5
  useAnimatedReaction,
6
6
  useSharedValue,
7
- withTiming,
8
7
  type SharedValue,
9
8
  } from 'react-native-reanimated';
10
9
  import { scheduleOnRN } from 'react-native-worklets';
@@ -24,6 +23,23 @@ export const getPositionWl = function (
24
23
  return (position - focalX) * scale + focalX + offsetX;
25
24
  };
26
25
 
26
+ /**
27
+ * Offset range keeping the content edges pinned to the viewport:
28
+ * leftBound (position 0) <= startOffset and rightBound (position width)
29
+ * >= width + startOffset. Collapses to [startOffset, startOffset] at scale 1.
30
+ */
31
+ export const getOffsetBoundsWl = function (
32
+ width: number,
33
+ startOffset: number,
34
+ focalX: number,
35
+ scale: number
36
+ ): [min: number, max: number] {
37
+ 'worklet';
38
+ const maxOffset = startOffset + focalX * (scale - 1);
39
+ const minOffset = startOffset + (1 - scale) * (width - focalX);
40
+ return [minOffset, maxOffset];
41
+ };
42
+
27
43
  export type AxisGestureProps = {
28
44
  width: number;
29
45
  startOffset?: number;
@@ -55,6 +71,8 @@ export const useScalableGesture = (props: AxisGestureProps): ScalableGesture =>
55
71
  // For panning
56
72
  const offsetX = useSharedValue(startOffset);
57
73
  const lastOffsetX = useSharedValue(startOffset);
74
+ // Pinch pivot bookkeeping (see pinchGesture)
75
+ const needsPivot = useSharedValue(true);
58
76
 
59
77
  const reset = useCallback(() => {
60
78
  scale.value = startScale;
@@ -74,54 +92,85 @@ export const useScalableGesture = (props: AxisGestureProps): ScalableGesture =>
74
92
  startScale,
75
93
  ]);
76
94
 
77
- const panGesture = Gesture.Pan()
78
- .onUpdate((event) => {
79
- const newOffsetX = lastOffsetX.value + event.translationX;
80
- offsetX.value = newOffsetX;
81
- })
82
- .onEnd(() => {
83
- lastScale.value = scale.value;
84
- lastFocalX.value = focalX.value;
85
-
86
- const leftBound = getPositionWl(
87
- 0,
88
- focalX.value,
89
- scale.value,
90
- offsetX.value
91
- );
92
- const rightBound = getPositionWl(
93
- width,
94
- focalX.value,
95
- scale.value,
96
- offsetX.value
97
- );
98
-
99
- let newOffset;
100
-
101
- if (leftBound > startOffset) {
102
- newOffset = offsetX.value - leftBound + startOffset;
103
- } else if (rightBound < width + startOffset) {
104
- newOffset = offsetX.value + width - rightBound + startOffset;
105
- } else {
106
- newOffset = offsetX.value;
107
- }
95
+ // Memoized: handing a new gesture instance to GestureDetector mid-gesture
96
+ // resets the active pan and makes the chart jump
97
+ const panGesture = useMemo(
98
+ () =>
99
+ Gesture.Pan()
100
+ // Single-finger only: two fingers belong to the pinch, whose focal
101
+ // rebase would otherwise fight the pan over offsetX
102
+ .maxPointers(1)
103
+ // Incremental deltas (changeX) with a live clamp keep the content
104
+ // edges pinned: no overscroll, no snap-back to interrupt
105
+ .onChange((event) => {
106
+ const [minOffset, maxOffset] = getOffsetBoundsWl(
107
+ width,
108
+ startOffset,
109
+ focalX.value,
110
+ scale.value
111
+ );
112
+ offsetX.value = clamp(
113
+ offsetX.value + event.changeX,
114
+ minOffset,
115
+ maxOffset
116
+ );
117
+ })
118
+ .onEnd(() => {
119
+ lastScale.value = scale.value;
120
+ lastFocalX.value = focalX.value;
121
+ lastOffsetX.value = offsetX.value;
122
+ }),
123
+ [focalX, lastFocalX, lastOffsetX, lastScale, offsetX, scale, startOffset, width]
124
+ );
108
125
 
109
- offsetX.value = withTiming(newOffset, { duration: 300 });
110
- lastOffsetX.value = newOffset;
111
- });
112
-
113
- const pinchGesture = Gesture.Pinch()
114
- .onUpdate((event) => {
115
- // Rebase offsetX so moving the focal point doesn't shift the content
116
- offsetX.value += (focalX.value - event.focalX) * (1 - scale.value);
117
- focalX.value = event.focalX;
118
- scale.value = clamp(lastScale.value * event.scale, 1, Infinity);
119
- })
120
- .onEnd(() => {
121
- lastScale.value = scale.value;
122
- lastFocalX.value = focalX.value;
123
- lastOffsetX.value = offsetX.value;
124
- });
126
+ const pinchGesture = useMemo(
127
+ () =>
128
+ Gesture.Pinch()
129
+ .onStart(() => {
130
+ needsPivot.value = true;
131
+ })
132
+ .onUpdate((event) => {
133
+ // The pinch can keep firing in the single-finger tail of the
134
+ // gesture, tracking the remaining finger as its focal: ignore it
135
+ // (numberOfPointers is missing on web — let those through)
136
+ if ((event.numberOfPointers ?? 2) < 2) {
137
+ needsPivot.value = true;
138
+ return;
139
+ }
140
+ const prevScale = scale.value;
141
+ const newScale = clamp(lastScale.value * event.scale, 1, Infinity);
142
+ if (needsPivot.value) {
143
+ // New pivot: rebase offsetX so re-anchoring under the fingers
144
+ // doesn't shift the content
145
+ const rebased =
146
+ offsetX.value + (focalX.value - event.focalX) * (1 - prevScale);
147
+ offsetX.value = (rebased * newScale) / prevScale;
148
+ needsPivot.value = false;
149
+ } else {
150
+ // Keep the content glued to both fingers: the offset follows the
151
+ // focal translation and rescales with the zoom
152
+ offsetX.value =
153
+ (event.focalX - focalX.value + offsetX.value / prevScale) *
154
+ newScale;
155
+ }
156
+ focalX.value = event.focalX;
157
+ scale.value = newScale;
158
+ // Zooming moves the bounds: keep the content edges pinned
159
+ const [minOffset, maxOffset] = getOffsetBoundsWl(
160
+ width,
161
+ startOffset,
162
+ focalX.value,
163
+ scale.value
164
+ );
165
+ offsetX.value = clamp(offsetX.value, minOffset, maxOffset);
166
+ })
167
+ .onEnd(() => {
168
+ lastScale.value = scale.value;
169
+ lastFocalX.value = focalX.value;
170
+ lastOffsetX.value = offsetX.value;
171
+ }),
172
+ [focalX, lastFocalX, lastOffsetX, lastScale, needsPivot, offsetX, scale, startOffset, width]
173
+ );
125
174
 
126
175
  return {
127
176
  scale,
@@ -143,6 +192,14 @@ export const useUpdateAxis = function (props: UpdateAxisProps) {
143
192
  const { scale, scales, onScaleChange } = props;
144
193
  const currentIndex = useSharedValue(0);
145
194
 
195
+ // Stable dispatcher so an inline onScaleChange doesn't re-register the
196
+ // reaction (and disturb the UI thread) on every render
197
+ const onScaleChangeRef = useRef(onScaleChange);
198
+ onScaleChangeRef.current = onScaleChange;
199
+ const dispatchScaleChange = useCallback((index: number) => {
200
+ onScaleChangeRef.current?.(index);
201
+ }, []);
202
+
146
203
  useAnimatedReaction(
147
204
  () => scale.value,
148
205
  (currentScale, _) => {
@@ -158,11 +215,12 @@ export const useUpdateAxis = function (props: UpdateAxisProps) {
158
215
  currentIndex.value !== i
159
216
  ) {
160
217
  currentIndex.value = i;
161
- if (onScaleChange !== undefined) scheduleOnRN(onScaleChange, i);
218
+ scheduleOnRN(dispatchScaleChange, i);
162
219
  break;
163
220
  }
164
221
  }
165
- }
222
+ },
223
+ [scales, dispatchScaleChange]
166
224
  );
167
225
 
168
226
  return { currentIndex };
@@ -198,6 +198,9 @@ export const useDotsTransition = function (props: UseDotAnimationProps) {
198
198
  }
199
199
  _dot.opacity.value = opacityWl(1);
200
200
  });
201
- }
201
+ },
202
+ // Explicit deps: without them the reaction re-registers and re-fires on
203
+ // every parent render, restarting every dot animation on the UI thread
204
+ [currentGraph, path, dataPoints, dots, opacityWl, translateWl]
202
205
  );
203
206
  };
@@ -1,11 +1,18 @@
1
1
  export { AxisLine, BottomAxis, Tick } from './BottomAxis';
2
2
  export type {
3
3
  AxisLineProps,
4
+ AxisTick,
4
5
  BottomAxisProps,
5
6
  TickProps,
6
7
  } from './BottomAxis';
7
8
  export { Cursor } from './Cursor';
8
9
  export type { CursorProps } from './Cursor';
10
+ export { Dot } from './Dot';
11
+ export type { DotProps } from './Dot';
12
+ export { Dots } from './Dots';
13
+ export type { DotShape, DotsProps } from './Dots';
14
+ export { YAxis, getPaddedTicks } from './YAxis';
15
+ export type { YAxisProps } from './YAxis';
9
16
  export { LineChart } from './Linechart';
10
17
  export type { LineChartProps, LinePath } from './Linechart';
11
18
  export { ScalablePath, ZoomableLineChart } from './ZoomableLinechart';
@@ -15,6 +22,7 @@ export type {
15
22
  } from './ZoomableLinechart';
16
23
  export {
17
24
  getClosestPoint,
25
+ getOffsetBoundsWl,
18
26
  getPositionWl,
19
27
  useCursorGesture,
20
28
  useScalableGesture,