react-native-hold-menu-actions 0.1.17 → 0.1.19
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/lib/commonjs/components/backdrop/Backdrop.js +0 -1
- package/lib/commonjs/components/backdrop/Backdrop.js.map +1 -1
- package/lib/commonjs/components/customView/CustomView.js +52 -54
- package/lib/commonjs/components/customView/CustomView.js.map +1 -1
- package/lib/commonjs/components/holdItem/HoldItem.js +11 -5
- package/lib/commonjs/components/holdItem/HoldItem.js.map +1 -1
- package/lib/commonjs/components/menu/Menu.js +2 -5
- package/lib/commonjs/components/menu/Menu.js.map +1 -1
- package/lib/commonjs/components/provider/Provider.js +0 -1
- package/lib/commonjs/components/provider/Provider.js.map +1 -1
- package/lib/commonjs/utils/calculations.js +1 -48
- package/lib/commonjs/utils/calculations.js.map +1 -1
- package/lib/module/components/backdrop/Backdrop.js +0 -1
- package/lib/module/components/backdrop/Backdrop.js.map +1 -1
- package/lib/module/components/customView/CustomView.js +54 -54
- package/lib/module/components/customView/CustomView.js.map +1 -1
- package/lib/module/components/holdItem/HoldItem.js +12 -6
- package/lib/module/components/holdItem/HoldItem.js.map +1 -1
- package/lib/module/components/menu/Menu.js +3 -5
- package/lib/module/components/menu/Menu.js.map +1 -1
- package/lib/module/components/provider/Provider.js +0 -1
- package/lib/module/components/provider/Provider.js.map +1 -1
- package/lib/module/utils/calculations.js +0 -44
- package/lib/module/utils/calculations.js.map +1 -1
- package/lib/typescript/components/customView/CustomView.d.ts +1 -1
- package/lib/typescript/components/menu/types.d.ts +0 -1
- package/lib/typescript/utils/calculations.d.ts +0 -1
- package/package.json +1 -1
- package/src/components/backdrop/Backdrop.tsx +0 -1
- package/src/components/customView/CustomView.tsx +69 -64
- package/src/components/holdItem/HoldItem.tsx +12 -16
- package/src/components/menu/Menu.tsx +2 -13
- package/src/components/menu/types.d.ts +0 -1
- package/src/components/provider/Provider.tsx +7 -10
- package/src/utils/calculations.ts +0 -53
|
@@ -32,50 +32,6 @@ export const menuAnimationAnchor = (anchorPoint, itemWidth, itemLength, itemsWit
|
|
|
32
32
|
}
|
|
33
33
|
};
|
|
34
34
|
};
|
|
35
|
-
export const calculateDynamicTransformValue = (itemY, itemHeight, menuHeight, customViewHeight, anchorPosition, screenHeight, safeAreaTop, safeAreaBottom) => {
|
|
36
|
-
'worklet';
|
|
37
|
-
|
|
38
|
-
const GAP = styleGuide.spacing;
|
|
39
|
-
const isAnchorTop = anchorPosition.includes('top');
|
|
40
|
-
|
|
41
|
-
if (isAnchorTop) {
|
|
42
|
-
// Menu below item, custom view above item
|
|
43
|
-
// Total space needed below item: itemHeight + gap + menuHeight + safeAreaBottom
|
|
44
|
-
// Total space needed above item: customViewHeight + gap + safeAreaTop
|
|
45
|
-
const bottomEdge = itemY + itemHeight + GAP + menuHeight + safeAreaBottom;
|
|
46
|
-
const topEdge = itemY - GAP - customViewHeight - safeAreaTop;
|
|
47
|
-
let tY = 0; // If bottom overflows screen
|
|
48
|
-
|
|
49
|
-
if (bottomEdge > screenHeight) {
|
|
50
|
-
tY = screenHeight - bottomEdge;
|
|
51
|
-
} // After shifting, check if custom view goes above safe area
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (topEdge + tY < 0) {
|
|
55
|
-
tY = -topEdge;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return tY;
|
|
59
|
-
} else {
|
|
60
|
-
// Menu above item, custom view below item
|
|
61
|
-
// Total space needed above item: menuHeight + gap + safeAreaTop
|
|
62
|
-
// Total space needed below item: itemHeight + gap + customViewHeight + safeAreaBottom
|
|
63
|
-
const topEdge = itemY - GAP - menuHeight - safeAreaTop;
|
|
64
|
-
const bottomEdge = itemY + itemHeight + GAP + customViewHeight + safeAreaBottom;
|
|
65
|
-
let tY = 0; // If top overflows screen (goes above safe area)
|
|
66
|
-
|
|
67
|
-
if (topEdge < 0) {
|
|
68
|
-
tY = -topEdge;
|
|
69
|
-
} // After shifting, check if custom view goes below screen
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (bottomEdge + tY > screenHeight) {
|
|
73
|
-
tY = screenHeight - bottomEdge;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return tY;
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
35
|
export const getTransformOrigin = (posX, itemWidth, windowWidth, bottom) => {
|
|
80
36
|
'worklet';
|
|
81
37
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["calculations.ts"],"names":["styleGuide","MENU_WIDTH","MENU_TRANSFORM_ORIGIN_TOLERENCE","FONT_SCALE","MenuItemHeight","typography","callout","lineHeight","spacing","calculateMenuHeight","itemLength","separatorCount","menuAnimationAnchor","anchorPoint","itemWidth","itemsWithSeparatorLength","MenuHeight","splittetAnchorName","split","Center1","Center2","TyTop1","TyTop2","TxLeft1","TxLeft2","beginningTransformations","translateX","translateY","endingTransformations","
|
|
1
|
+
{"version":3,"sources":["calculations.ts"],"names":["styleGuide","MENU_WIDTH","MENU_TRANSFORM_ORIGIN_TOLERENCE","FONT_SCALE","MenuItemHeight","typography","callout","lineHeight","spacing","calculateMenuHeight","itemLength","separatorCount","menuAnimationAnchor","anchorPoint","itemWidth","itemsWithSeparatorLength","MenuHeight","splittetAnchorName","split","Center1","Center2","TyTop1","TyTop2","TxLeft1","TxLeft2","beginningTransformations","translateX","translateY","endingTransformations","getTransformOrigin","posX","windowWidth","bottom","distanceToLeft","Math","round","distanceToRight","position","majority","abs"],"mappings":"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","sourcesContent":["import styleGuide from '../styleGuide';\nimport {\n MENU_WIDTH,\n MENU_TRANSFORM_ORIGIN_TOLERENCE,\n FONT_SCALE,\n} from '../constants';\n\nexport const MenuItemHeight = () => {\n 'worklet';\n return (\n styleGuide.typography.callout.lineHeight * FONT_SCALE +\n styleGuide.spacing * 2.5\n );\n};\n\nexport const calculateMenuHeight = (\n itemLength: number,\n separatorCount: number\n) => {\n 'worklet';\n return (\n MenuItemHeight() * itemLength +\n (itemLength - 1) +\n separatorCount * styleGuide.spacing\n );\n};\n\nexport type TransformOriginAnchorPosition =\n | 'top-right'\n | 'top-left'\n | 'top-center'\n | 'bottom-right'\n | 'bottom-left'\n | 'bottom-center';\n\nexport const menuAnimationAnchor = (\n anchorPoint: TransformOriginAnchorPosition,\n itemWidth: number,\n itemLength: number,\n itemsWithSeparatorLength: number\n) => {\n 'worklet';\n const MenuHeight = calculateMenuHeight(itemLength, itemsWithSeparatorLength);\n const splittetAnchorName: string[] = anchorPoint.split('-');\n\n const Center1 = itemWidth;\n const Center2 = 0;\n\n const TyTop1 = -(MenuHeight / 2);\n const TyTop2 = MenuHeight / 2;\n\n const TxLeft1 = (MENU_WIDTH / 2) * -1;\n const TxLeft2 = (MENU_WIDTH / 2) * 1;\n\n return {\n beginningTransformations: {\n translateX:\n splittetAnchorName[1] === 'right'\n ? -TxLeft1\n : splittetAnchorName[1] === 'left'\n ? TxLeft1\n : Center1,\n translateY:\n splittetAnchorName[0] === 'top'\n ? TyTop1\n : splittetAnchorName[0] === 'bottom'\n ? TyTop1\n : Center2,\n },\n endingTransformations: {\n translateX:\n splittetAnchorName[1] === 'right'\n ? -TxLeft2\n : splittetAnchorName[1] === 'left'\n ? TxLeft2\n : Center2,\n translateY:\n splittetAnchorName[0] === 'top'\n ? TyTop2\n : splittetAnchorName[0] === 'bottom'\n ? -TyTop2\n : Center2,\n },\n };\n};\n\nexport const getTransformOrigin = (\n posX: number,\n itemWidth: number,\n windowWidth: number,\n bottom?: boolean\n): TransformOriginAnchorPosition => {\n 'worklet';\n const distanceToLeft = Math.round(posX + itemWidth / 2);\n const distanceToRight = Math.round(windowWidth - distanceToLeft);\n\n let position: TransformOriginAnchorPosition = bottom\n ? 'bottom-right'\n : 'top-right';\n\n const majority = Math.abs(distanceToLeft - distanceToRight);\n\n if (majority < MENU_TRANSFORM_ORIGIN_TOLERENCE) {\n position = bottom ? 'bottom-center' : 'top-center';\n } else if (distanceToLeft < distanceToRight) {\n position = bottom ? 'bottom-left' : 'top-left';\n }\n\n return position;\n};\n"]}
|
|
@@ -11,5 +11,4 @@ export declare const menuAnimationAnchor: (anchorPoint: TransformOriginAnchorPos
|
|
|
11
11
|
translateY: number;
|
|
12
12
|
};
|
|
13
13
|
};
|
|
14
|
-
export declare const calculateDynamicTransformValue: (itemY: number, itemHeight: number, menuHeight: number, customViewHeight: number, anchorPosition: TransformOriginAnchorPosition, screenHeight: number, safeAreaTop: number, safeAreaBottom: number) => number;
|
|
15
14
|
export declare const getTransformOrigin: (posX: number, itemWidth: number, windowWidth: number, bottom?: boolean | undefined) => TransformOriginAnchorPosition;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-hold-menu-actions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"description": "A performant, easy to use hold to open context menu for React Native powered by Reanimated.",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -5,7 +5,7 @@ import Animated, {
|
|
|
5
5
|
runOnJS,
|
|
6
6
|
useAnimatedReaction,
|
|
7
7
|
useAnimatedStyle,
|
|
8
|
-
|
|
8
|
+
withDelay,
|
|
9
9
|
withSpring,
|
|
10
10
|
withTiming,
|
|
11
11
|
} from 'react-native-reanimated';
|
|
@@ -17,45 +17,35 @@ import {
|
|
|
17
17
|
SPRING_CONFIGURATION,
|
|
18
18
|
SPRING_CONFIGURATION_MENU,
|
|
19
19
|
WINDOW_WIDTH,
|
|
20
|
-
WINDOW_HEIGHT,
|
|
21
20
|
} from '../../constants';
|
|
22
21
|
import { RenderCustomView } from '../menu/types';
|
|
23
|
-
import { calculateDynamicTransformValue } from '../../utils/calculations';
|
|
24
|
-
|
|
25
|
-
const SCREEN_PADDING = 16;
|
|
26
22
|
|
|
27
23
|
const CustomViewComponent = () => {
|
|
28
|
-
const { state, menuProps, customViewRef
|
|
24
|
+
const { state, menuProps, customViewRef } = useInternal();
|
|
29
25
|
|
|
30
26
|
const [renderFn, setRenderFn] = useState<RenderCustomView | null>(null);
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
const cvWidth = useSharedValue(0);
|
|
27
|
+
const [customViewHeight, setCustomViewHeight] = useState(0);
|
|
28
|
+
const [customViewWidth, setCustomViewWidth] = useState(0);
|
|
34
29
|
|
|
35
30
|
const closeMenu = useCallback(() => {
|
|
36
31
|
state.value = CONTEXT_MENU_STATE.END;
|
|
37
32
|
}, [state]);
|
|
38
33
|
|
|
39
|
-
const onLayout = useCallback(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
menuProps.value = { ...menuProps.value, customViewHeight: height };
|
|
45
|
-
},
|
|
46
|
-
[menuProps, cvHeight, cvWidth]
|
|
47
|
-
);
|
|
34
|
+
const onLayout = useCallback((event: LayoutChangeEvent) => {
|
|
35
|
+
const { height, width } = event.nativeEvent.layout;
|
|
36
|
+
setCustomViewHeight(height);
|
|
37
|
+
setCustomViewWidth(width);
|
|
38
|
+
}, []);
|
|
48
39
|
|
|
49
40
|
const clearRenderFn = useCallback(() => {
|
|
50
41
|
setRenderFn(null);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}, [menuProps, cvHeight, cvWidth]);
|
|
42
|
+
setCustomViewHeight(0);
|
|
43
|
+
setCustomViewWidth(0);
|
|
44
|
+
}, []);
|
|
55
45
|
|
|
56
46
|
const updateRenderFn = useCallback(
|
|
57
|
-
(
|
|
58
|
-
if (
|
|
47
|
+
(hasCustomView: boolean) => {
|
|
48
|
+
if (hasCustomView && customViewRef.current) {
|
|
59
49
|
setRenderFn(() => customViewRef.current);
|
|
60
50
|
} else {
|
|
61
51
|
setTimeout(clearRenderFn, HOLD_ITEM_TRANSFORM_DURATION);
|
|
@@ -79,71 +69,87 @@ const CustomViewComponent = () => {
|
|
|
79
69
|
[state, menuProps]
|
|
80
70
|
);
|
|
81
71
|
|
|
82
|
-
const visibilityStyles = useAnimatedStyle(() => {
|
|
83
|
-
const active =
|
|
84
|
-
state.value === CONTEXT_MENU_STATE.ACTIVE &&
|
|
85
|
-
menuProps.value.hasCustomView;
|
|
86
|
-
return {
|
|
87
|
-
opacity: withTiming(active ? 1 : 0, {
|
|
88
|
-
duration: HOLD_ITEM_TRANSFORM_DURATION,
|
|
89
|
-
}),
|
|
90
|
-
transform: [
|
|
91
|
-
{
|
|
92
|
-
scale: active
|
|
93
|
-
? withSpring(1, SPRING_CONFIGURATION_MENU)
|
|
94
|
-
: withTiming(0, { duration: HOLD_ITEM_TRANSFORM_DURATION }),
|
|
95
|
-
},
|
|
96
|
-
],
|
|
97
|
-
};
|
|
98
|
-
});
|
|
99
|
-
|
|
100
72
|
const wrapperStyles = useAnimatedStyle(() => {
|
|
101
73
|
const anchorPositionVertical = menuProps.value.anchorPosition.split('-')[0];
|
|
102
74
|
const isAbove = anchorPositionVertical === 'top';
|
|
103
75
|
|
|
76
|
+
// Fixed anchor point: top of the item
|
|
104
77
|
const top = menuProps.value.itemY;
|
|
78
|
+
|
|
79
|
+
const SCREEN_PADDING = 16;
|
|
105
80
|
const MAX_WIDTH = WINDOW_WIDTH - SCREEN_PADDING * 2;
|
|
106
81
|
|
|
82
|
+
// Start at item's left edge
|
|
107
83
|
let left = menuProps.value.itemX;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
84
|
+
|
|
85
|
+
// If the view overflows the right edge, shift left
|
|
86
|
+
if (
|
|
87
|
+
customViewWidth > 0 &&
|
|
88
|
+
left + customViewWidth > WINDOW_WIDTH - SCREEN_PADDING
|
|
89
|
+
) {
|
|
90
|
+
left = Math.max(
|
|
91
|
+
SCREEN_PADDING,
|
|
92
|
+
WINDOW_WIDTH - customViewWidth - SCREEN_PADDING
|
|
93
|
+
);
|
|
111
94
|
}
|
|
112
95
|
if (left < SCREEN_PADDING) {
|
|
113
96
|
left = SCREEN_PADDING;
|
|
114
97
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
menuProps.value.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
98
|
+
const tY = menuProps.value.transformValue;
|
|
99
|
+
|
|
100
|
+
// Positional offset via translateY:
|
|
101
|
+
// above item: shift up by customViewHeight + gap
|
|
102
|
+
// below item: shift down by itemHeight + gap
|
|
103
|
+
const positionOffsetY = isAbove
|
|
104
|
+
? -(customViewHeight + 8)
|
|
105
|
+
: menuProps.value.itemHeight + 8;
|
|
106
|
+
|
|
107
|
+
const scaleAnimation =
|
|
108
|
+
state.value === CONTEXT_MENU_STATE.ACTIVE
|
|
109
|
+
? withDelay(150, withSpring(1, SPRING_CONFIGURATION_MENU))
|
|
110
|
+
: withTiming(0, { duration: HOLD_ITEM_TRANSFORM_DURATION });
|
|
111
|
+
|
|
112
|
+
const opacityAnimation = withDelay(
|
|
113
|
+
150,
|
|
114
|
+
withTiming(state.value === CONTEXT_MENU_STATE.ACTIVE ? 1 : 0, {
|
|
115
|
+
duration: HOLD_ITEM_TRANSFORM_DURATION,
|
|
116
|
+
})
|
|
126
117
|
);
|
|
127
118
|
|
|
128
|
-
|
|
119
|
+
// Scale anchor: scale from the edge closest to the item
|
|
120
|
+
const scaleAnchorOffset = isAbove
|
|
121
|
+
? customViewHeight / 2
|
|
122
|
+
: -(customViewHeight / 2);
|
|
129
123
|
|
|
130
124
|
return {
|
|
131
125
|
top,
|
|
132
126
|
left,
|
|
133
127
|
maxWidth: MAX_WIDTH,
|
|
128
|
+
opacity: opacityAnimation,
|
|
134
129
|
transform: [
|
|
135
|
-
|
|
130
|
+
// 1. Transform value (screen boundary compensation)
|
|
131
|
+
{
|
|
132
|
+
translateY:
|
|
133
|
+
state.value === CONTEXT_MENU_STATE.ACTIVE
|
|
134
|
+
? withSpring(tY, SPRING_CONFIGURATION)
|
|
135
|
+
: withTiming(0, { duration: HOLD_ITEM_TRANSFORM_DURATION }),
|
|
136
|
+
},
|
|
137
|
+
// 2. Position offset (animated when customViewHeight changes)
|
|
136
138
|
{ translateY: withSpring(positionOffsetY, SPRING_CONFIGURATION_MENU) },
|
|
139
|
+
// 3. Scale anchor: move to edge -> scale -> move back
|
|
140
|
+
{ translateY: scaleAnchorOffset },
|
|
141
|
+
{ scale: scaleAnimation },
|
|
142
|
+
{ translateY: -scaleAnchorOffset },
|
|
137
143
|
],
|
|
138
144
|
};
|
|
139
|
-
});
|
|
145
|
+
}, [menuProps, customViewHeight, customViewWidth]);
|
|
146
|
+
|
|
147
|
+
if (!renderFn) return null;
|
|
140
148
|
|
|
141
149
|
return (
|
|
142
150
|
<Animated.View style={[styles.customViewWrapper, wrapperStyles]}>
|
|
143
|
-
<Animated.View
|
|
144
|
-
|
|
145
|
-
{renderFn?.({ closeMenu })}
|
|
146
|
-
</Animated.View>
|
|
151
|
+
<Animated.View onLayout={onLayout}>
|
|
152
|
+
{renderFn({ closeMenu })}
|
|
147
153
|
</Animated.View>
|
|
148
154
|
</Animated.View>
|
|
149
155
|
);
|
|
@@ -153,7 +159,6 @@ const styles = StyleSheet.create({
|
|
|
153
159
|
customViewWrapper: {
|
|
154
160
|
position: 'absolute',
|
|
155
161
|
zIndex: 20,
|
|
156
|
-
pointerEvents: 'box-none',
|
|
157
162
|
},
|
|
158
163
|
});
|
|
159
164
|
|
|
@@ -35,7 +35,6 @@ import {
|
|
|
35
35
|
TransformOriginAnchorPosition,
|
|
36
36
|
getTransformOrigin,
|
|
37
37
|
calculateMenuHeight,
|
|
38
|
-
calculateDynamicTransformValue,
|
|
39
38
|
} from '../../utils/calculations';
|
|
40
39
|
import {
|
|
41
40
|
HOLD_ITEM_TRANSFORM_DURATION,
|
|
@@ -152,9 +151,18 @@ const HoldItemComponent = ({
|
|
|
152
151
|
const calculateTransformValue = () => {
|
|
153
152
|
'worklet';
|
|
154
153
|
|
|
155
|
-
const
|
|
154
|
+
const screenH =
|
|
156
155
|
deviceOrientation === 'portrait' ? WINDOW_HEIGHT : WINDOW_WIDTH;
|
|
157
156
|
|
|
157
|
+
const hasCustomView = !!renderCustomView;
|
|
158
|
+
|
|
159
|
+
// If custom view exists, center item on screen
|
|
160
|
+
if (hasCustomView && !disableMove) {
|
|
161
|
+
const itemCenterY = itemRectY.value + itemRectHeight.value / 2;
|
|
162
|
+
const screenCenterY = screenH / 2;
|
|
163
|
+
return screenCenterY - itemCenterY;
|
|
164
|
+
}
|
|
165
|
+
|
|
158
166
|
const isAnchorPointTop = transformOrigin.value.includes('top');
|
|
159
167
|
|
|
160
168
|
let tY = 0;
|
|
@@ -167,7 +175,7 @@ const HoldItemComponent = ({
|
|
|
167
175
|
styleGuide.spacing +
|
|
168
176
|
(safeAreaInsets?.bottom || 0);
|
|
169
177
|
|
|
170
|
-
tY = topTransform >
|
|
178
|
+
tY = topTransform > screenH ? screenH - topTransform : 0;
|
|
171
179
|
} else {
|
|
172
180
|
const bottomTransform =
|
|
173
181
|
itemRectY.value - menuHeight - (safeAreaInsets?.top || 0);
|
|
@@ -196,7 +204,6 @@ const HoldItemComponent = ({
|
|
|
196
204
|
menuHeight: menuHeight,
|
|
197
205
|
items: items || [],
|
|
198
206
|
transformValue: transformValue.value,
|
|
199
|
-
customViewHeight: 0,
|
|
200
207
|
actionParams: actionParams || {},
|
|
201
208
|
hasCustomView: !!renderCustomView,
|
|
202
209
|
};
|
|
@@ -334,18 +341,7 @@ const HoldItemComponent = ({
|
|
|
334
341
|
const animateOpacity = () =>
|
|
335
342
|
withDelay(HOLD_ITEM_TRANSFORM_DURATION, withTiming(0, { duration: 0 }));
|
|
336
343
|
|
|
337
|
-
|
|
338
|
-
deviceOrientation === 'portrait' ? WINDOW_HEIGHT : WINDOW_WIDTH;
|
|
339
|
-
let tY = calculateDynamicTransformValue(
|
|
340
|
-
itemRectY.value,
|
|
341
|
-
itemRectHeight.value,
|
|
342
|
-
menuHeight,
|
|
343
|
-
menuProps.value.customViewHeight,
|
|
344
|
-
transformOrigin.value,
|
|
345
|
-
screenH,
|
|
346
|
-
safeAreaInsets?.top || 0,
|
|
347
|
-
safeAreaInsets?.bottom || 0
|
|
348
|
-
);
|
|
344
|
+
let tY = calculateTransformValue();
|
|
349
345
|
const transformAnimation = () =>
|
|
350
346
|
disableMove
|
|
351
347
|
? 0
|
|
@@ -14,12 +14,10 @@ import {
|
|
|
14
14
|
HOLD_ITEM_TRANSFORM_DURATION,
|
|
15
15
|
CONTEXT_MENU_STATE,
|
|
16
16
|
SPRING_CONFIGURATION,
|
|
17
|
-
WINDOW_HEIGHT,
|
|
18
17
|
} from '../../constants';
|
|
19
|
-
import { calculateDynamicTransformValue } from '../../utils/calculations';
|
|
20
18
|
|
|
21
19
|
const MenuComponent = () => {
|
|
22
|
-
const { state, menuProps
|
|
20
|
+
const { state, menuProps } = useInternal();
|
|
23
21
|
|
|
24
22
|
const wrapperStyles = useAnimatedStyle(() => {
|
|
25
23
|
const anchorPositionVertical = menuProps.value.anchorPosition.split('-')[0];
|
|
@@ -30,16 +28,7 @@ const MenuComponent = () => {
|
|
|
30
28
|
: menuProps.value.itemY - 8;
|
|
31
29
|
const left = menuProps.value.itemX;
|
|
32
30
|
const width = menuProps.value.itemWidth;
|
|
33
|
-
const tY =
|
|
34
|
-
menuProps.value.itemY,
|
|
35
|
-
menuProps.value.itemHeight,
|
|
36
|
-
menuProps.value.menuHeight,
|
|
37
|
-
menuProps.value.customViewHeight,
|
|
38
|
-
menuProps.value.anchorPosition,
|
|
39
|
-
WINDOW_HEIGHT,
|
|
40
|
-
safeAreaInsets?.top || 0,
|
|
41
|
-
safeAreaInsets?.bottom || 0
|
|
42
|
-
);
|
|
31
|
+
const tY = menuProps.value.transformValue;
|
|
43
32
|
|
|
44
33
|
return {
|
|
45
34
|
top,
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import React, { memo, useEffect, useMemo, useRef } from 'react';
|
|
2
2
|
import { PortalProvider } from '@gorhom/portal';
|
|
3
|
-
import Animated, {
|
|
4
|
-
useSharedValue,
|
|
5
|
-
useAnimatedReaction,
|
|
6
|
-
runOnJS,
|
|
7
|
-
} from 'react-native-reanimated';
|
|
3
|
+
import Animated, { useSharedValue, useAnimatedReaction, runOnJS } from 'react-native-reanimated';
|
|
8
4
|
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
9
5
|
|
|
10
6
|
// Components
|
|
@@ -51,7 +47,6 @@ const ProviderComponent = ({
|
|
|
51
47
|
anchorPosition: 'top-center',
|
|
52
48
|
menuHeight: 0,
|
|
53
49
|
transformValue: 0,
|
|
54
|
-
customViewHeight: 0,
|
|
55
50
|
actionParams: {},
|
|
56
51
|
hasCustomView: false,
|
|
57
52
|
});
|
|
@@ -67,12 +62,14 @@ const ProviderComponent = ({
|
|
|
67
62
|
state => {
|
|
68
63
|
switch (state) {
|
|
69
64
|
case CONTEXT_MENU_STATE.ACTIVE: {
|
|
70
|
-
if (onOpen)
|
|
71
|
-
|
|
65
|
+
if (onOpen)
|
|
66
|
+
runOnJS(onOpen)();
|
|
67
|
+
break
|
|
72
68
|
}
|
|
73
69
|
case CONTEXT_MENU_STATE.END: {
|
|
74
|
-
if (onClose)
|
|
75
|
-
|
|
70
|
+
if (onClose)
|
|
71
|
+
runOnJS(onClose)();
|
|
72
|
+
break
|
|
76
73
|
}
|
|
77
74
|
}
|
|
78
75
|
},
|
|
@@ -84,59 +84,6 @@ export const menuAnimationAnchor = (
|
|
|
84
84
|
};
|
|
85
85
|
};
|
|
86
86
|
|
|
87
|
-
export const calculateDynamicTransformValue = (
|
|
88
|
-
itemY: number,
|
|
89
|
-
itemHeight: number,
|
|
90
|
-
menuHeight: number,
|
|
91
|
-
customViewHeight: number,
|
|
92
|
-
anchorPosition: TransformOriginAnchorPosition,
|
|
93
|
-
screenHeight: number,
|
|
94
|
-
safeAreaTop: number,
|
|
95
|
-
safeAreaBottom: number
|
|
96
|
-
): number => {
|
|
97
|
-
'worklet';
|
|
98
|
-
const GAP = styleGuide.spacing;
|
|
99
|
-
const isAnchorTop = anchorPosition.includes('top');
|
|
100
|
-
|
|
101
|
-
if (isAnchorTop) {
|
|
102
|
-
// Menu below item, custom view above item
|
|
103
|
-
// Total space needed below item: itemHeight + gap + menuHeight + safeAreaBottom
|
|
104
|
-
// Total space needed above item: customViewHeight + gap + safeAreaTop
|
|
105
|
-
const bottomEdge = itemY + itemHeight + GAP + menuHeight + safeAreaBottom;
|
|
106
|
-
const topEdge = itemY - GAP - customViewHeight - safeAreaTop;
|
|
107
|
-
|
|
108
|
-
let tY = 0;
|
|
109
|
-
|
|
110
|
-
// If bottom overflows screen
|
|
111
|
-
if (bottomEdge > screenHeight) {
|
|
112
|
-
tY = screenHeight - bottomEdge;
|
|
113
|
-
}
|
|
114
|
-
// After shifting, check if custom view goes above safe area
|
|
115
|
-
if (topEdge + tY < 0) {
|
|
116
|
-
tY = -topEdge;
|
|
117
|
-
}
|
|
118
|
-
return tY;
|
|
119
|
-
} else {
|
|
120
|
-
// Menu above item, custom view below item
|
|
121
|
-
// Total space needed above item: menuHeight + gap + safeAreaTop
|
|
122
|
-
// Total space needed below item: itemHeight + gap + customViewHeight + safeAreaBottom
|
|
123
|
-
const topEdge = itemY - GAP - menuHeight - safeAreaTop;
|
|
124
|
-
const bottomEdge =
|
|
125
|
-
itemY + itemHeight + GAP + customViewHeight + safeAreaBottom;
|
|
126
|
-
|
|
127
|
-
let tY = 0;
|
|
128
|
-
// If top overflows screen (goes above safe area)
|
|
129
|
-
if (topEdge < 0) {
|
|
130
|
-
tY = -topEdge;
|
|
131
|
-
}
|
|
132
|
-
// After shifting, check if custom view goes below screen
|
|
133
|
-
if (bottomEdge + tY > screenHeight) {
|
|
134
|
-
tY = screenHeight - bottomEdge;
|
|
135
|
-
}
|
|
136
|
-
return tY;
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
|
|
140
87
|
export const getTransformOrigin = (
|
|
141
88
|
posX: number,
|
|
142
89
|
itemWidth: number,
|