react-native-hold-menu-actions 0.1.15 → 0.1.17
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 +1 -0
- package/lib/commonjs/components/backdrop/Backdrop.js.map +1 -1
- package/lib/commonjs/components/customView/CustomView.js +54 -52
- package/lib/commonjs/components/customView/CustomView.js.map +1 -1
- package/lib/commonjs/components/holdItem/HoldItem.js +3 -1
- package/lib/commonjs/components/holdItem/HoldItem.js.map +1 -1
- package/lib/commonjs/components/menu/Menu.js +5 -2
- package/lib/commonjs/components/menu/Menu.js.map +1 -1
- package/lib/commonjs/components/provider/Provider.js +1 -0
- package/lib/commonjs/components/provider/Provider.js.map +1 -1
- package/lib/commonjs/utils/calculations.js +48 -1
- package/lib/commonjs/utils/calculations.js.map +1 -1
- package/lib/module/components/backdrop/Backdrop.js +1 -0
- 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 +4 -2
- package/lib/module/components/holdItem/HoldItem.js.map +1 -1
- package/lib/module/components/menu/Menu.js +5 -3
- package/lib/module/components/menu/Menu.js.map +1 -1
- package/lib/module/components/provider/Provider.js +1 -0
- package/lib/module/components/provider/Provider.js.map +1 -1
- package/lib/module/utils/calculations.js +44 -0
- 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 +1 -0
- package/lib/typescript/utils/calculations.d.ts +1 -0
- package/package.json +1 -1
- package/src/components/backdrop/Backdrop.tsx +1 -0
- package/src/components/customView/CustomView.tsx +64 -66
- package/src/components/holdItem/HoldItem.tsx +14 -1
- package/src/components/menu/Menu.tsx +13 -2
- package/src/components/menu/types.d.ts +1 -0
- package/src/components/provider/Provider.tsx +10 -7
- package/src/utils/calculations.ts +53 -0
|
@@ -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","getTransformOrigin","posX","windowWidth","bottom","distanceToLeft","Math","round","distanceToRight","position","majority","abs"],"mappings":"AAAA,OAAOA,UAAP,MAAuB,eAAvB;AACA,SACEC,UADF,EAEEC,+BAFF,EAGEC,UAHF,QAIO,cAJP;AAMA,OAAO,MAAMC,cAAc,GAAG,MAAM;AAClC;;AACA,SACEJ,UAAU,CAACK,UAAX,CAAsBC,OAAtB,CAA8BC,UAA9B,GAA2CJ,UAA3C,GACAH,UAAU,CAACQ,OAAX,GAAqB,GAFvB;AAID,CANM;AAQP,OAAO,MAAMC,mBAAmB,GAAG,CACjCC,UADiC,EAEjCC,cAFiC,KAG9B;AACH;;AACA,SACEP,cAAc,KAAKM,UAAnB,IACCA,UAAU,GAAG,CADd,IAEAC,cAAc,GAAGX,UAAU,CAACQ,OAH9B;AAKD,CAVM;AAoBP,OAAO,MAAMI,mBAAmB,GAAG,CACjCC,WADiC,EAEjCC,SAFiC,EAGjCJ,UAHiC,EAIjCK,wBAJiC,KAK9B;AACH;;AACA,QAAMC,UAAU,GAAGP,mBAAmB,CAACC,UAAD,EAAaK,wBAAb,CAAtC;AACA,QAAME,kBAA4B,GAAGJ,WAAW,CAACK,KAAZ,CAAkB,GAAlB,CAArC;AAEA,QAAMC,OAAO,GAAGL,SAAhB;AACA,QAAMM,OAAO,GAAG,CAAhB;AAEA,QAAMC,MAAM,GAAG,EAAEL,UAAU,GAAG,CAAf,CAAf;AACA,QAAMM,MAAM,GAAGN,UAAU,GAAG,CAA5B;AAEA,QAAMO,OAAO,GAAItB,UAAU,GAAG,CAAd,GAAmB,CAAC,CAApC;AACA,QAAMuB,OAAO,GAAIvB,UAAU,GAAG,CAAd,GAAmB,CAAnC;AAEA,SAAO;AACLwB,IAAAA,wBAAwB,EAAE;AACxBC,MAAAA,UAAU,EACRT,kBAAkB,CAAC,CAAD,CAAlB,KAA0B,OAA1B,GACI,CAACM,OADL,GAEIN,kBAAkB,CAAC,CAAD,CAAlB,KAA0B,MAA1B,GACAM,OADA,GAEAJ,OANkB;AAOxBQ,MAAAA,UAAU,EACRV,kBAAkB,CAAC,CAAD,CAAlB,KAA0B,KAA1B,GACII,MADJ,GAEIJ,kBAAkB,CAAC,CAAD,CAAlB,KAA0B,QAA1B,GACAI,MADA,GAEAD;AAZkB,KADrB;AAeLQ,IAAAA,qBAAqB,EAAE;AACrBF,MAAAA,UAAU,EACRT,kBAAkB,CAAC,CAAD,CAAlB,KAA0B,OAA1B,GACI,CAACO,OADL,GAEIP,kBAAkB,CAAC,CAAD,CAAlB,KAA0B,MAA1B,GACAO,OADA,GAEAJ,OANe;AAOrBO,MAAAA,UAAU,EACRV,kBAAkB,CAAC,CAAD,CAAlB,KAA0B,KAA1B,GACIK,MADJ,GAEIL,kBAAkB,CAAC,CAAD,CAAlB,KAA0B,QAA1B,GACA,CAACK,MADD,GAEAF;AAZe;AAflB,GAAP;AA8BD,CAjDM;AAmDP,OAAO,MAAMS,kBAAkB,GAAG,CAChCC,IADgC,
|
|
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","calculateDynamicTransformValue","itemY","itemHeight","menuHeight","customViewHeight","anchorPosition","screenHeight","safeAreaTop","safeAreaBottom","GAP","isAnchorTop","includes","bottomEdge","topEdge","tY","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 calculateDynamicTransformValue = (\n itemY: number,\n itemHeight: number,\n menuHeight: number,\n customViewHeight: number,\n anchorPosition: TransformOriginAnchorPosition,\n screenHeight: number,\n safeAreaTop: number,\n safeAreaBottom: number\n): number => {\n 'worklet';\n const GAP = styleGuide.spacing;\n const isAnchorTop = anchorPosition.includes('top');\n\n if (isAnchorTop) {\n // Menu below item, custom view above item\n // Total space needed below item: itemHeight + gap + menuHeight + safeAreaBottom\n // Total space needed above item: customViewHeight + gap + safeAreaTop\n const bottomEdge = itemY + itemHeight + GAP + menuHeight + safeAreaBottom;\n const topEdge = itemY - GAP - customViewHeight - safeAreaTop;\n\n let tY = 0;\n\n // If bottom overflows screen\n if (bottomEdge > screenHeight) {\n tY = screenHeight - bottomEdge;\n }\n // After shifting, check if custom view goes above safe area\n if (topEdge + tY < 0) {\n tY = -topEdge;\n }\n return tY;\n } else {\n // Menu above item, custom view below item\n // Total space needed above item: menuHeight + gap + safeAreaTop\n // Total space needed below item: itemHeight + gap + customViewHeight + safeAreaBottom\n const topEdge = itemY - GAP - menuHeight - safeAreaTop;\n const bottomEdge =\n itemY + itemHeight + GAP + customViewHeight + safeAreaBottom;\n\n let tY = 0;\n // If top overflows screen (goes above safe area)\n if (topEdge < 0) {\n tY = -topEdge;\n }\n // After shifting, check if custom view goes below screen\n if (bottomEdge + tY > screenHeight) {\n tY = screenHeight - bottomEdge;\n }\n return tY;\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,4 +11,5 @@ 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;
|
|
14
15
|
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.17",
|
|
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,6 +5,7 @@ import Animated, {
|
|
|
5
5
|
runOnJS,
|
|
6
6
|
useAnimatedReaction,
|
|
7
7
|
useAnimatedStyle,
|
|
8
|
+
useSharedValue,
|
|
8
9
|
withSpring,
|
|
9
10
|
withTiming,
|
|
10
11
|
} from 'react-native-reanimated';
|
|
@@ -16,35 +17,45 @@ import {
|
|
|
16
17
|
SPRING_CONFIGURATION,
|
|
17
18
|
SPRING_CONFIGURATION_MENU,
|
|
18
19
|
WINDOW_WIDTH,
|
|
20
|
+
WINDOW_HEIGHT,
|
|
19
21
|
} from '../../constants';
|
|
20
22
|
import { RenderCustomView } from '../menu/types';
|
|
23
|
+
import { calculateDynamicTransformValue } from '../../utils/calculations';
|
|
24
|
+
|
|
25
|
+
const SCREEN_PADDING = 16;
|
|
21
26
|
|
|
22
27
|
const CustomViewComponent = () => {
|
|
23
|
-
const { state, menuProps, customViewRef } = useInternal();
|
|
28
|
+
const { state, menuProps, customViewRef, safeAreaInsets } = useInternal();
|
|
24
29
|
|
|
25
30
|
const [renderFn, setRenderFn] = useState<RenderCustomView | null>(null);
|
|
26
|
-
|
|
27
|
-
const
|
|
31
|
+
|
|
32
|
+
const cvHeight = useSharedValue(0);
|
|
33
|
+
const cvWidth = useSharedValue(0);
|
|
28
34
|
|
|
29
35
|
const closeMenu = useCallback(() => {
|
|
30
36
|
state.value = CONTEXT_MENU_STATE.END;
|
|
31
37
|
}, [state]);
|
|
32
38
|
|
|
33
|
-
const onLayout = useCallback(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
const onLayout = useCallback(
|
|
40
|
+
(event: LayoutChangeEvent) => {
|
|
41
|
+
const { height, width } = event.nativeEvent.layout;
|
|
42
|
+
cvHeight.value = height;
|
|
43
|
+
cvWidth.value = width;
|
|
44
|
+
menuProps.value = { ...menuProps.value, customViewHeight: height };
|
|
45
|
+
},
|
|
46
|
+
[menuProps, cvHeight, cvWidth]
|
|
47
|
+
);
|
|
38
48
|
|
|
39
49
|
const clearRenderFn = useCallback(() => {
|
|
40
50
|
setRenderFn(null);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
cvHeight.value = 0;
|
|
52
|
+
cvWidth.value = 0;
|
|
53
|
+
menuProps.value = { ...menuProps.value, customViewHeight: 0 };
|
|
54
|
+
}, [menuProps, cvHeight, cvWidth]);
|
|
44
55
|
|
|
45
56
|
const updateRenderFn = useCallback(
|
|
46
|
-
(
|
|
47
|
-
if (
|
|
57
|
+
(shouldShow: boolean) => {
|
|
58
|
+
if (shouldShow && customViewRef.current) {
|
|
48
59
|
setRenderFn(() => customViewRef.current);
|
|
49
60
|
} else {
|
|
50
61
|
setTimeout(clearRenderFn, HOLD_ITEM_TRANSFORM_DURATION);
|
|
@@ -68,85 +79,71 @@ const CustomViewComponent = () => {
|
|
|
68
79
|
[state, menuProps]
|
|
69
80
|
);
|
|
70
81
|
|
|
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
|
+
|
|
71
100
|
const wrapperStyles = useAnimatedStyle(() => {
|
|
72
101
|
const anchorPositionVertical = menuProps.value.anchorPosition.split('-')[0];
|
|
73
102
|
const isAbove = anchorPositionVertical === 'top';
|
|
74
103
|
|
|
75
|
-
// Fixed anchor point: top of the item
|
|
76
104
|
const top = menuProps.value.itemY;
|
|
77
|
-
|
|
78
|
-
const SCREEN_PADDING = 16;
|
|
79
105
|
const MAX_WIDTH = WINDOW_WIDTH - SCREEN_PADDING * 2;
|
|
80
106
|
|
|
81
|
-
// Start at item's left edge
|
|
82
107
|
let left = menuProps.value.itemX;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
customViewWidth > 0 &&
|
|
87
|
-
left + customViewWidth > WINDOW_WIDTH - SCREEN_PADDING
|
|
88
|
-
) {
|
|
89
|
-
left = Math.max(
|
|
90
|
-
SCREEN_PADDING,
|
|
91
|
-
WINDOW_WIDTH - customViewWidth - SCREEN_PADDING
|
|
92
|
-
);
|
|
108
|
+
const w = cvWidth.value;
|
|
109
|
+
if (w > 0 && left + w > WINDOW_WIDTH - SCREEN_PADDING) {
|
|
110
|
+
left = Math.max(SCREEN_PADDING, WINDOW_WIDTH - w - SCREEN_PADDING);
|
|
93
111
|
}
|
|
94
112
|
if (left < SCREEN_PADDING) {
|
|
95
113
|
left = SCREEN_PADDING;
|
|
96
114
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
? withSpring(1, SPRING_CONFIGURATION_MENU)
|
|
109
|
-
: withTiming(0, { duration: HOLD_ITEM_TRANSFORM_DURATION });
|
|
110
|
-
|
|
111
|
-
const opacityAnimation = withTiming(
|
|
112
|
-
state.value === CONTEXT_MENU_STATE.ACTIVE ? 1 : 0,
|
|
113
|
-
{ duration: HOLD_ITEM_TRANSFORM_DURATION }
|
|
115
|
+
|
|
116
|
+
const h = cvHeight.value;
|
|
117
|
+
const tY = calculateDynamicTransformValue(
|
|
118
|
+
menuProps.value.itemY,
|
|
119
|
+
menuProps.value.itemHeight,
|
|
120
|
+
menuProps.value.menuHeight,
|
|
121
|
+
h,
|
|
122
|
+
menuProps.value.anchorPosition,
|
|
123
|
+
WINDOW_HEIGHT,
|
|
124
|
+
safeAreaInsets?.top || 0,
|
|
125
|
+
safeAreaInsets?.bottom || 0
|
|
114
126
|
);
|
|
115
127
|
|
|
116
|
-
|
|
117
|
-
const scaleAnchorOffset = isAbove
|
|
118
|
-
? customViewHeight / 2
|
|
119
|
-
: -(customViewHeight / 2);
|
|
128
|
+
const positionOffsetY = isAbove ? -(h + 8) : menuProps.value.itemHeight + 8;
|
|
120
129
|
|
|
121
130
|
return {
|
|
122
131
|
top,
|
|
123
132
|
left,
|
|
124
133
|
maxWidth: MAX_WIDTH,
|
|
125
|
-
opacity: opacityAnimation,
|
|
126
134
|
transform: [
|
|
127
|
-
|
|
128
|
-
{
|
|
129
|
-
translateY:
|
|
130
|
-
state.value === CONTEXT_MENU_STATE.ACTIVE
|
|
131
|
-
? withSpring(tY, SPRING_CONFIGURATION)
|
|
132
|
-
: withTiming(0, { duration: HOLD_ITEM_TRANSFORM_DURATION }),
|
|
133
|
-
},
|
|
134
|
-
// 2. Position offset (animated when customViewHeight changes)
|
|
135
|
+
{ translateY: withSpring(tY, SPRING_CONFIGURATION) },
|
|
135
136
|
{ translateY: withSpring(positionOffsetY, SPRING_CONFIGURATION_MENU) },
|
|
136
|
-
// 3. Scale anchor: move to edge -> scale -> move back
|
|
137
|
-
{ translateY: scaleAnchorOffset },
|
|
138
|
-
{ scale: scaleAnimation },
|
|
139
|
-
{ translateY: -scaleAnchorOffset },
|
|
140
137
|
],
|
|
141
138
|
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (!renderFn) return null;
|
|
139
|
+
});
|
|
145
140
|
|
|
146
141
|
return (
|
|
147
142
|
<Animated.View style={[styles.customViewWrapper, wrapperStyles]}>
|
|
148
|
-
<Animated.View
|
|
149
|
-
{
|
|
143
|
+
<Animated.View style={visibilityStyles}>
|
|
144
|
+
<Animated.View onLayout={onLayout}>
|
|
145
|
+
{renderFn?.({ closeMenu })}
|
|
146
|
+
</Animated.View>
|
|
150
147
|
</Animated.View>
|
|
151
148
|
</Animated.View>
|
|
152
149
|
);
|
|
@@ -156,6 +153,7 @@ const styles = StyleSheet.create({
|
|
|
156
153
|
customViewWrapper: {
|
|
157
154
|
position: 'absolute',
|
|
158
155
|
zIndex: 20,
|
|
156
|
+
pointerEvents: 'box-none',
|
|
159
157
|
},
|
|
160
158
|
});
|
|
161
159
|
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
TransformOriginAnchorPosition,
|
|
36
36
|
getTransformOrigin,
|
|
37
37
|
calculateMenuHeight,
|
|
38
|
+
calculateDynamicTransformValue,
|
|
38
39
|
} from '../../utils/calculations';
|
|
39
40
|
import {
|
|
40
41
|
HOLD_ITEM_TRANSFORM_DURATION,
|
|
@@ -195,6 +196,7 @@ const HoldItemComponent = ({
|
|
|
195
196
|
menuHeight: menuHeight,
|
|
196
197
|
items: items || [],
|
|
197
198
|
transformValue: transformValue.value,
|
|
199
|
+
customViewHeight: 0,
|
|
198
200
|
actionParams: actionParams || {},
|
|
199
201
|
hasCustomView: !!renderCustomView,
|
|
200
202
|
};
|
|
@@ -332,7 +334,18 @@ const HoldItemComponent = ({
|
|
|
332
334
|
const animateOpacity = () =>
|
|
333
335
|
withDelay(HOLD_ITEM_TRANSFORM_DURATION, withTiming(0, { duration: 0 }));
|
|
334
336
|
|
|
335
|
-
|
|
337
|
+
const screenH =
|
|
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
|
+
);
|
|
336
349
|
const transformAnimation = () =>
|
|
337
350
|
disableMove
|
|
338
351
|
? 0
|
|
@@ -14,10 +14,12 @@ import {
|
|
|
14
14
|
HOLD_ITEM_TRANSFORM_DURATION,
|
|
15
15
|
CONTEXT_MENU_STATE,
|
|
16
16
|
SPRING_CONFIGURATION,
|
|
17
|
+
WINDOW_HEIGHT,
|
|
17
18
|
} from '../../constants';
|
|
19
|
+
import { calculateDynamicTransformValue } from '../../utils/calculations';
|
|
18
20
|
|
|
19
21
|
const MenuComponent = () => {
|
|
20
|
-
const { state, menuProps } = useInternal();
|
|
22
|
+
const { state, menuProps, safeAreaInsets } = useInternal();
|
|
21
23
|
|
|
22
24
|
const wrapperStyles = useAnimatedStyle(() => {
|
|
23
25
|
const anchorPositionVertical = menuProps.value.anchorPosition.split('-')[0];
|
|
@@ -28,7 +30,16 @@ const MenuComponent = () => {
|
|
|
28
30
|
: menuProps.value.itemY - 8;
|
|
29
31
|
const left = menuProps.value.itemX;
|
|
30
32
|
const width = menuProps.value.itemWidth;
|
|
31
|
-
const tY =
|
|
33
|
+
const tY = calculateDynamicTransformValue(
|
|
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
|
+
);
|
|
32
43
|
|
|
33
44
|
return {
|
|
34
45
|
top,
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import React, { memo, useEffect, useMemo, useRef } from 'react';
|
|
2
2
|
import { PortalProvider } from '@gorhom/portal';
|
|
3
|
-
import Animated, {
|
|
3
|
+
import Animated, {
|
|
4
|
+
useSharedValue,
|
|
5
|
+
useAnimatedReaction,
|
|
6
|
+
runOnJS,
|
|
7
|
+
} from 'react-native-reanimated';
|
|
4
8
|
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
5
9
|
|
|
6
10
|
// Components
|
|
@@ -47,6 +51,7 @@ const ProviderComponent = ({
|
|
|
47
51
|
anchorPosition: 'top-center',
|
|
48
52
|
menuHeight: 0,
|
|
49
53
|
transformValue: 0,
|
|
54
|
+
customViewHeight: 0,
|
|
50
55
|
actionParams: {},
|
|
51
56
|
hasCustomView: false,
|
|
52
57
|
});
|
|
@@ -62,14 +67,12 @@ const ProviderComponent = ({
|
|
|
62
67
|
state => {
|
|
63
68
|
switch (state) {
|
|
64
69
|
case CONTEXT_MENU_STATE.ACTIVE: {
|
|
65
|
-
if (onOpen)
|
|
66
|
-
|
|
67
|
-
break
|
|
70
|
+
if (onOpen) runOnJS(onOpen)();
|
|
71
|
+
break;
|
|
68
72
|
}
|
|
69
73
|
case CONTEXT_MENU_STATE.END: {
|
|
70
|
-
if (onClose)
|
|
71
|
-
|
|
72
|
-
break
|
|
74
|
+
if (onClose) runOnJS(onClose)();
|
|
75
|
+
break;
|
|
73
76
|
}
|
|
74
77
|
}
|
|
75
78
|
},
|
|
@@ -84,6 +84,59 @@ 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
|
+
|
|
87
140
|
export const getTransformOrigin = (
|
|
88
141
|
posX: number,
|
|
89
142
|
itemWidth: number,
|