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.
Files changed (35) hide show
  1. package/lib/commonjs/components/backdrop/Backdrop.js +1 -0
  2. package/lib/commonjs/components/backdrop/Backdrop.js.map +1 -1
  3. package/lib/commonjs/components/customView/CustomView.js +54 -52
  4. package/lib/commonjs/components/customView/CustomView.js.map +1 -1
  5. package/lib/commonjs/components/holdItem/HoldItem.js +3 -1
  6. package/lib/commonjs/components/holdItem/HoldItem.js.map +1 -1
  7. package/lib/commonjs/components/menu/Menu.js +5 -2
  8. package/lib/commonjs/components/menu/Menu.js.map +1 -1
  9. package/lib/commonjs/components/provider/Provider.js +1 -0
  10. package/lib/commonjs/components/provider/Provider.js.map +1 -1
  11. package/lib/commonjs/utils/calculations.js +48 -1
  12. package/lib/commonjs/utils/calculations.js.map +1 -1
  13. package/lib/module/components/backdrop/Backdrop.js +1 -0
  14. package/lib/module/components/backdrop/Backdrop.js.map +1 -1
  15. package/lib/module/components/customView/CustomView.js +54 -54
  16. package/lib/module/components/customView/CustomView.js.map +1 -1
  17. package/lib/module/components/holdItem/HoldItem.js +4 -2
  18. package/lib/module/components/holdItem/HoldItem.js.map +1 -1
  19. package/lib/module/components/menu/Menu.js +5 -3
  20. package/lib/module/components/menu/Menu.js.map +1 -1
  21. package/lib/module/components/provider/Provider.js +1 -0
  22. package/lib/module/components/provider/Provider.js.map +1 -1
  23. package/lib/module/utils/calculations.js +44 -0
  24. package/lib/module/utils/calculations.js.map +1 -1
  25. package/lib/typescript/components/customView/CustomView.d.ts +1 -1
  26. package/lib/typescript/components/menu/types.d.ts +1 -0
  27. package/lib/typescript/utils/calculations.d.ts +1 -0
  28. package/package.json +1 -1
  29. package/src/components/backdrop/Backdrop.tsx +1 -0
  30. package/src/components/customView/CustomView.tsx +64 -66
  31. package/src/components/holdItem/HoldItem.tsx +14 -1
  32. package/src/components/menu/Menu.tsx +13 -2
  33. package/src/components/menu/types.d.ts +1 -0
  34. package/src/components/provider/Provider.tsx +10 -7
  35. 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":"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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"]}
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"]}
@@ -1,3 +1,3 @@
1
1
  import React from 'react';
2
- declare const CustomView: React.MemoExoticComponent<() => JSX.Element | null>;
2
+ declare const CustomView: React.MemoExoticComponent<() => JSX.Element>;
3
3
  export default CustomView;
@@ -28,6 +28,7 @@ export type MenuInternalProps = {
28
28
  anchorPosition: TransformOriginAnchorPosition;
29
29
  menuHeight: number;
30
30
  transformValue: number;
31
+ customViewHeight: number;
31
32
  actionParams: {
32
33
  [name: string]: (string | number)[];
33
34
  };
@@ -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.15",
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",
@@ -117,6 +117,7 @@ const BackdropComponent = () => {
117
117
  <AnimatedBlurView
118
118
  // @ts-ignore
119
119
  tint="default"
120
+ blurType={'dark'}
120
121
  animatedProps={animatedContainerProps}
121
122
  style={[styles.container, animatedContainerStyle]}
122
123
  >
@@ -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
- const [customViewHeight, setCustomViewHeight] = useState(0);
27
- const [customViewWidth, setCustomViewWidth] = useState(0);
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((event: LayoutChangeEvent) => {
34
- const { height, width } = event.nativeEvent.layout;
35
- setCustomViewHeight(height);
36
- setCustomViewWidth(width);
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
- setCustomViewHeight(0);
42
- setCustomViewWidth(0);
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
- (hasCustomView: boolean) => {
47
- if (hasCustomView && customViewRef.current) {
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
- // If the view overflows the right edge, shift left
85
- if (
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
- const tY = menuProps.value.transformValue;
98
-
99
- // Positional offset via translateY:
100
- // above item: shift up by customViewHeight + gap
101
- // below item: shift down by itemHeight + gap
102
- const positionOffsetY = isAbove
103
- ? -(customViewHeight + 8)
104
- : menuProps.value.itemHeight + 8;
105
-
106
- const scaleAnimation =
107
- state.value === CONTEXT_MENU_STATE.ACTIVE
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
- // Scale anchor: scale from the edge closest to the item
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
- // 1. Transform value (screen boundary compensation)
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
- }, [menuProps, customViewHeight, customViewWidth]);
143
-
144
- if (!renderFn) return null;
139
+ });
145
140
 
146
141
  return (
147
142
  <Animated.View style={[styles.customViewWrapper, wrapperStyles]}>
148
- <Animated.View onLayout={onLayout}>
149
- {renderFn({ closeMenu })}
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
- let tY = calculateTransformValue();
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 = menuProps.value.transformValue;
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,
@@ -28,6 +28,7 @@ export type MenuInternalProps = {
28
28
  anchorPosition: TransformOriginAnchorPosition;
29
29
  menuHeight: number;
30
30
  transformValue: number;
31
+ customViewHeight: number;
31
32
  actionParams: {
32
33
  [name: string]: (string | number)[];
33
34
  };
@@ -1,6 +1,10 @@
1
1
  import React, { memo, useEffect, useMemo, useRef } from 'react';
2
2
  import { PortalProvider } from '@gorhom/portal';
3
- import Animated, { useSharedValue, useAnimatedReaction, runOnJS } from 'react-native-reanimated';
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
- runOnJS(onOpen)();
67
- break
70
+ if (onOpen) runOnJS(onOpen)();
71
+ break;
68
72
  }
69
73
  case CONTEXT_MENU_STATE.END: {
70
- if (onClose)
71
- runOnJS(onClose)();
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,