diff --git a/README.md b/README.md index 334d2d3d..aff8878e 100644 --- a/README.md +++ b/README.md @@ -291,6 +291,40 @@ It will contain id of the given action. |-------------------------|----------| | ({nativeEvent}) => void | No | +### Events + +#### `onCloseMenu` + +Callback function that will be called when the menu is dismissed. This event fires at the start of the dismissal, before any animations complete. + +| Type | Required | +|------------|----------| +| () => void | No | + +#### `onOpenMenu` + +Callback function that will be called when the menu is opened. This event fires right before the menu is displayed. + +| Type | Required | +|------------|----------| +| () => void | No | + +Example usage: +```jsx + { + console.log('Menu was opened'); + }} + onCloseMenu={() => { + console.log('Menu was closed'); + }} + // ... other props +> + + Open Menu + + +``` ## Testing with Jest In some cases, you might want to mock the package to test your components. You can do this by using the `jest.mock` function. diff --git a/android/src/main/java/com/reactnativemenu/MenuOnCloseEvent.kt b/android/src/main/java/com/reactnativemenu/MenuOnCloseEvent.kt new file mode 100644 index 00000000..7a585cbb --- /dev/null +++ b/android/src/main/java/com/reactnativemenu/MenuOnCloseEvent.kt @@ -0,0 +1,13 @@ +package com.reactnativemenu + +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.WritableMap +import com.facebook.react.uimanager.events.Event + +class MenuOnCloseEvent(surfaceId: Int, viewId: Int, private val targetId: Int) : Event(surfaceId, viewId) { + override fun getEventName() = "onCloseMenu" + + override fun getEventData(): WritableMap? { + return Arguments.createMap() + } +} \ No newline at end of file diff --git a/android/src/main/java/com/reactnativemenu/MenuOnOpenEvent.kt b/android/src/main/java/com/reactnativemenu/MenuOnOpenEvent.kt new file mode 100644 index 00000000..89a63019 --- /dev/null +++ b/android/src/main/java/com/reactnativemenu/MenuOnOpenEvent.kt @@ -0,0 +1,13 @@ +package com.reactnativemenu + +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.WritableMap +import com.facebook.react.uimanager.events.Event + +class MenuOnOpenEvent(surfaceId: Int, viewId: Int, private val targetId: Int) : Event(surfaceId, viewId) { + override fun getEventName() = "onOpenMenu" + + override fun getEventData(): WritableMap? { + return Arguments.createMap() + } +} \ No newline at end of file diff --git a/android/src/main/java/com/reactnativemenu/MenuView.kt b/android/src/main/java/com/reactnativemenu/MenuView.kt index 0b3dbe59..e0f93aab 100644 --- a/android/src/main/java/com/reactnativemenu/MenuView.kt +++ b/android/src/main/java/com/reactnativemenu/MenuView.kt @@ -261,8 +261,14 @@ class MenuView(private val mContext: ReactContext) : ReactViewGroup(mContext) { } mPopupMenu.setOnDismissListener { mIsMenuDisplayed = false + val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(mContext, id) + val surfaceId: Int = UIManagerHelper.getSurfaceId(this) + dispatcher?.dispatchEvent(MenuOnCloseEvent(surfaceId, id, id)) } mIsMenuDisplayed = true + val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(mContext, id) + val surfaceId: Int = UIManagerHelper.getSurfaceId(this) + dispatcher?.dispatchEvent(MenuOnOpenEvent(surfaceId, id, id)) mPopupMenu.show() } } diff --git a/android/src/main/java/com/reactnativemenu/MenuViewManagerBase.kt b/android/src/main/java/com/reactnativemenu/MenuViewManagerBase.kt index 2b2b0797..9089217e 100644 --- a/android/src/main/java/com/reactnativemenu/MenuViewManagerBase.kt +++ b/android/src/main/java/com/reactnativemenu/MenuViewManagerBase.kt @@ -17,7 +17,7 @@ import com.facebook.react.views.view.ReactDrawableHelper import com.facebook.react.views.view.ReactViewGroup import com.facebook.yoga.YogaConstants -abstract class MenuViewManagerBase: ReactClippingViewManager() { +abstract class MenuViewManagerBase : ReactClippingViewManager() { override fun getName() = "MenuView" @ReactProp(name = "actions") @@ -37,8 +37,12 @@ abstract class MenuViewManagerBase: ReactClippingViewManager() { override fun getExportedCustomDirectEventTypeConstants(): MutableMap { return MapBuilder.of( - "onPressAction", - MapBuilder.of("registrationName", "onPressAction") + "onPressAction", + MapBuilder.of("registrationName", "onPressAction"), + "onCloseMenu", + MapBuilder.of("registrationName", "onCloseMenu"), + "onOpenMenu", + MapBuilder.of("registrationName", "onOpenMenu") ) } @@ -82,7 +86,19 @@ abstract class MenuViewManagerBase: ReactClippingViewManager() { view.nextFocusUpId = viewId } - @ReactPropGroup(names = [ViewProps.BORDER_RADIUS, ViewProps.BORDER_TOP_LEFT_RADIUS, ViewProps.BORDER_TOP_RIGHT_RADIUS, ViewProps.BORDER_BOTTOM_RIGHT_RADIUS, ViewProps.BORDER_BOTTOM_LEFT_RADIUS, ViewProps.BORDER_TOP_START_RADIUS, ViewProps.BORDER_TOP_END_RADIUS, ViewProps.BORDER_BOTTOM_START_RADIUS, ViewProps.BORDER_BOTTOM_END_RADIUS]) + @ReactPropGroup( + names = + [ + ViewProps.BORDER_RADIUS, + ViewProps.BORDER_TOP_LEFT_RADIUS, + ViewProps.BORDER_TOP_RIGHT_RADIUS, + ViewProps.BORDER_BOTTOM_RIGHT_RADIUS, + ViewProps.BORDER_BOTTOM_LEFT_RADIUS, + ViewProps.BORDER_TOP_START_RADIUS, + ViewProps.BORDER_TOP_END_RADIUS, + ViewProps.BORDER_BOTTOM_START_RADIUS, + ViewProps.BORDER_BOTTOM_END_RADIUS] + ) fun setBorderRadius(view: ReactViewGroup, index: Int, borderRadius: Float) { var borderRadius = borderRadius if (!YogaConstants.isUndefined(borderRadius) && borderRadius < 0) { @@ -109,33 +125,60 @@ abstract class MenuViewManagerBase: ReactClippingViewManager() { // We should keep using setters as `Val cannot be reassigned` view.setHitSlopRect(null) } else { - view.setHitSlopRect(Rect( - if (hitSlop.hasKey("left")) PixelUtil.toPixelFromDIP(hitSlop.getDouble("left")).toInt() else 0, - if (hitSlop.hasKey("top")) PixelUtil.toPixelFromDIP(hitSlop.getDouble("top")).toInt() else 0, - if (hitSlop.hasKey("right")) PixelUtil.toPixelFromDIP(hitSlop.getDouble("right")).toInt() else 0, - if (hitSlop.hasKey("bottom")) PixelUtil.toPixelFromDIP(hitSlop.getDouble("bottom")).toInt() else 0)) + view.setHitSlopRect( + Rect( + if (hitSlop.hasKey("left")) + PixelUtil.toPixelFromDIP(hitSlop.getDouble("left")).toInt() + else 0, + if (hitSlop.hasKey("top")) + PixelUtil.toPixelFromDIP(hitSlop.getDouble("top")).toInt() + else 0, + if (hitSlop.hasKey("right")) + PixelUtil.toPixelFromDIP(hitSlop.getDouble("right")).toInt() + else 0, + if (hitSlop.hasKey("bottom")) + PixelUtil.toPixelFromDIP(hitSlop.getDouble("bottom")).toInt() + else 0 + ) + ) } } @ReactProp(name = "nativeBackgroundAndroid") fun setNativeBackground(view: ReactViewGroup, @Nullable bg: ReadableMap?) { view.setTranslucentBackgroundDrawable( - if (bg == null) null else ReactDrawableHelper.createDrawableFromJSDescription(view.context, bg)) + if (bg == null) null + else ReactDrawableHelper.createDrawableFromJSDescription(view.context, bg) + ) } @TargetApi(Build.VERSION_CODES.M) @ReactProp(name = "nativeForegroundAndroid") fun setNativeForeground(view: ReactViewGroup, @Nullable fg: ReadableMap?) { - view.foreground = if (fg == null) null else ReactDrawableHelper.createDrawableFromJSDescription(view.context, fg) + view.foreground = + if (fg == null) null + else ReactDrawableHelper.createDrawableFromJSDescription(view.context, fg) } @ReactProp(name = ViewProps.NEEDS_OFFSCREEN_ALPHA_COMPOSITING) fun setNeedsOffscreenAlphaCompositing( - view: ReactViewGroup, needsOffscreenAlphaCompositing: Boolean) { + view: ReactViewGroup, + needsOffscreenAlphaCompositing: Boolean + ) { view.setNeedsOffscreenAlphaCompositing(needsOffscreenAlphaCompositing) } - @ReactPropGroup(names = [ViewProps.BORDER_WIDTH, ViewProps.BORDER_LEFT_WIDTH, ViewProps.BORDER_RIGHT_WIDTH, ViewProps.BORDER_TOP_WIDTH, ViewProps.BORDER_BOTTOM_WIDTH, ViewProps.BORDER_START_WIDTH, ViewProps.BORDER_END_WIDTH]) + @ReactPropGroup( + names = + [ + ViewProps.BORDER_WIDTH, + ViewProps.BORDER_LEFT_WIDTH, + ViewProps.BORDER_RIGHT_WIDTH, + ViewProps.BORDER_TOP_WIDTH, + ViewProps.BORDER_BOTTOM_WIDTH, + ViewProps.BORDER_START_WIDTH, + ViewProps.BORDER_END_WIDTH] + ) fun setBorderWidth(view: ReactViewGroup, index: Int, width: Float) { var width = width if (!YogaConstants.isUndefined(width) && width < 0) { @@ -147,7 +190,18 @@ abstract class MenuViewManagerBase: ReactClippingViewManager() { view.setBorderWidth(SPACING_TYPES[index], width) } - @ReactPropGroup(names = [ViewProps.BORDER_COLOR, ViewProps.BORDER_LEFT_COLOR, ViewProps.BORDER_RIGHT_COLOR, ViewProps.BORDER_TOP_COLOR, ViewProps.BORDER_BOTTOM_COLOR, ViewProps.BORDER_START_COLOR, ViewProps.BORDER_END_COLOR], customType = "Color") + @ReactPropGroup( + names = + [ + ViewProps.BORDER_COLOR, + ViewProps.BORDER_LEFT_COLOR, + ViewProps.BORDER_RIGHT_COLOR, + ViewProps.BORDER_TOP_COLOR, + ViewProps.BORDER_BOTTOM_COLOR, + ViewProps.BORDER_START_COLOR, + ViewProps.BORDER_END_COLOR], + customType = "Color" + ) abstract fun setBorderColor(view: ReactViewGroup, index: Int, color: Int?) @ReactProp(name = ViewProps.OVERFLOW) @@ -181,14 +235,15 @@ abstract class MenuViewManagerBase: ReactClippingViewManager() { companion object { val COMMAND_SHOW = 1 - val SPACING_TYPES = arrayOf( - Spacing.ALL, - Spacing.LEFT, - Spacing.RIGHT, - Spacing.TOP, - Spacing.BOTTOM, - Spacing.START, - Spacing.END - ) + val SPACING_TYPES = + arrayOf( + Spacing.ALL, + Spacing.LEFT, + Spacing.RIGHT, + Spacing.TOP, + Spacing.BOTTOM, + Spacing.START, + Spacing.END + ) } } diff --git a/ios/MenuViewManager.mm b/ios/MenuViewManager.mm index 41a968e2..3d149b3e 100644 --- a/ios/MenuViewManager.mm +++ b/ios/MenuViewManager.mm @@ -57,6 +57,14 @@ - (UIView *)view * onPressAction: callback to be called once user selects an action */ RCT_EXPORT_VIEW_PROPERTY(onPressAction, RCTDirectEventBlock); +/** + * onCloseMenu: callback to be called when the menu is closed + */ +RCT_EXPORT_VIEW_PROPERTY(onCloseMenu, RCTDirectEventBlock); +/** + * onOpenMenu: callback to be called when the menu is opened + */ +RCT_EXPORT_VIEW_PROPERTY(onOpenMenu, RCTDirectEventBlock); /** * shouldOpenOnLongPress: determines whether menu should be opened after long press or normal press */ diff --git a/ios/NewArch/FabricActionSheetView.swift b/ios/NewArch/FabricActionSheetView.swift index 74d2f48f..484ff8a1 100644 --- a/ios/NewArch/FabricActionSheetView.swift +++ b/ios/NewArch/FabricActionSheetView.swift @@ -1,11 +1,23 @@ - @objc(FabricActionSheetView) public class FabricActionSheetView: ActionSheetView, FabricViewImplementationProtocol { public var onPressAction: ((String) -> Void)? - + public var onCloseMenu: (() -> Void)? + public var onOpenMenu: (() -> Void)? + @objc override func sendButtonAction(_ action: String) { if let onPress = onPressAction { onPress(action) } } + + @objc override func sendMenuClose() { + if let onCloseMenu = onCloseMenu { + onCloseMenu() + } + } + @objc override func sendMenuOpen() { + if let onOpenMenu = onOpenMenu { + onOpenMenu() + } + } } diff --git a/ios/NewArch/FabricMenuViewImplementation.swift b/ios/NewArch/FabricMenuViewImplementation.swift index 03df036d..33d27c5a 100644 --- a/ios/NewArch/FabricMenuViewImplementation.swift +++ b/ios/NewArch/FabricMenuViewImplementation.swift @@ -10,11 +10,25 @@ import UIKit @objc(FabricMenuViewImplementation) public class FabricMenuViewImplementation: MenuViewImplementation, FabricViewImplementationProtocol { public var onPressAction: ((String) -> Void)? - + public var onCloseMenu: (() -> Void)? + public var onOpenMenu: (() -> Void)? + @objc override func sendButtonAction(_ action: UIAction) { if let onPress = onPressAction { onPress(action.identifier.rawValue) } } + @objc override func sendMenuClose() { + if let onCloseMenu = onCloseMenu { + onCloseMenu() + } + } + + @objc override func sendMenuOpen() { + if let onOpenMenu = onOpenMenu { + onOpenMenu() + } + } + } diff --git a/ios/NewArch/FabricViewImplementationProtocol.swift b/ios/NewArch/FabricViewImplementationProtocol.swift index 80353c6c..2cef2fb8 100644 --- a/ios/NewArch/FabricViewImplementationProtocol.swift +++ b/ios/NewArch/FabricViewImplementationProtocol.swift @@ -6,4 +6,6 @@ import Foundation var shouldOpenOnLongPress: Bool { get set } @objc optional var hitSlop: UIEdgeInsets { get set } var onPressAction: ((String) -> Void)? { get set } + var onCloseMenu: (() -> Void)? { get set } + var onOpenMenu: (() -> Void)? { get set } } diff --git a/ios/NewArch/MenuView.mm b/ios/NewArch/MenuView.mm index da06dfd8..6e815e49 100644 --- a/ios/NewArch/MenuView.mm +++ b/ios/NewArch/MenuView.mm @@ -41,6 +41,9 @@ - (instancetype)initWithFrame:(CGRect)frame _view.onPressAction = ^(NSString *eventString) { [self onPressAction:eventString]; }; + _view.onCloseMenu = ^{ + [self onCloseMenu]; + }; self.contentView = _view; } @@ -68,6 +71,22 @@ - (void)onPressAction:(NSString * _Nonnull)eventString { } } +- (void)onCloseMenu { + // If screen is already unmounted then there will be no event emitter + const auto eventEmitter = [self getEventEmitter]; + if (eventEmitter != nullptr) { + eventEmitter->onCloseMenu({}); + } +} + +- (void)onOpenMenu { + // If screen is already unmounted then there will be no event emitter + const auto eventEmitter = [self getEventEmitter]; + if (eventEmitter != nullptr) { + eventEmitter->onOpenMenu({}); + } +} + /** Responsible for iterating through the C++ vector and convert each struct element to NSDictionary, then return it all in an NSArray */ diff --git a/ios/OldArch/LegacyActionSheetView.swift b/ios/OldArch/LegacyActionSheetView.swift index a46ff502..3cd1a251 100644 --- a/ios/OldArch/LegacyActionSheetView.swift +++ b/ios/OldArch/LegacyActionSheetView.swift @@ -1,11 +1,26 @@ - @objc(LegacyActionSheetView) public class LegacyActionSheetView: ActionSheetView { @objc var onPressAction: RCTDirectEventBlock? - + @objc var onCloseMenu: RCTDirectEventBlock? + @objc var onOpenMenu: RCTDirectEventBlock? + + + @objc override func sendButtonAction(_ action: String) { if let onPress = onPressAction { onPress(["event":action]) } } + + @objc override func sendMenuClose() { + if let onCloseMenu = onCloseMenu { + onCloseMenu([:]) + } + } + + @objc override func sendMenuOpen() { + if let onOpenMenu = onOpenMenu { + onOpenMenu([:]) + } + } } diff --git a/ios/OldArch/LegacyMenuViewImplementation.swift b/ios/OldArch/LegacyMenuViewImplementation.swift index 6329cf2a..6a2a205f 100644 --- a/ios/OldArch/LegacyMenuViewImplementation.swift +++ b/ios/OldArch/LegacyMenuViewImplementation.swift @@ -3,6 +3,8 @@ import UIKit @objc(LegacyMenuViewImplementation) public class LegacyMenuViewImplementation: MenuViewImplementation { @objc var onPressAction: RCTDirectEventBlock? + @objc var onCloseMenu: RCTDirectEventBlock? + @objc var onOpenMenu: RCTDirectEventBlock? @objc override func sendButtonAction(_ action: UIAction) { if let onPress = onPressAction { @@ -10,4 +12,16 @@ public class LegacyMenuViewImplementation: MenuViewImplementation { } } + @objc override func sendMenuClose() { + if let onCloseMenu = onCloseMenu { + onCloseMenu([:]) + } + } + + @objc override func sendMenuOpen() { + if let onOpenMenu = onOpenMenu { + onOpenMenu([:]) + } + } + } diff --git a/ios/Shared/ActionSheetView.swift b/ios/Shared/ActionSheetView.swift index c521b19d..352d8fa6 100644 --- a/ios/Shared/ActionSheetView.swift +++ b/ios/Shared/ActionSheetView.swift @@ -26,6 +26,7 @@ public class ActionSheetView: UIView { actions.forEach({ alertAction in if let action = RCTAlertAction(details: alertAction).createAction({ event in self.sendButtonAction(event) + self.sendMenuClose() }) { _actions.append(action) } @@ -49,6 +50,7 @@ public class ActionSheetView: UIView { } func launchActionSheet() { + self.sendMenuOpen() let alert = UIAlertController(title: _title, message: nil, preferredStyle: .actionSheet) @@ -68,7 +70,9 @@ public class ActionSheetView: UIView { alert.addAction(action.copy() as! UIAlertAction) }) - alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in + self.sendMenuClose() + })) if UIDevice.current.userInterfaceIdiom == .pad { alert.modalPresentationStyle = .popover @@ -109,6 +113,14 @@ public class ActionSheetView: UIView { // NO-OP (should be overriden by parent) } + @objc func sendMenuClose() { + // NO-OP (should be overriden by parent) + } + + @objc func sendMenuOpen() { + // NO-OP (should be overriden by parent) + } + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/ios/Shared/MenuViewImplementation.swift b/ios/Shared/MenuViewImplementation.swift index ccfc5810..5c4e0da4 100644 --- a/ios/Shared/MenuViewImplementation.swift +++ b/ios/Shared/MenuViewImplementation.swift @@ -53,13 +53,28 @@ public class MenuViewImplementation: UIButton { @objc public var hitSlop: UIEdgeInsets = .zero override init(frame: CGRect) { - super.init(frame: frame) - self.setup() + super.init(frame: frame) + let interaction = UIContextMenuInteraction(delegate: self) + self.addInteraction(interaction) + self.setup() + } + + public override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? { + sendMenuOpen() + return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { [weak self] _ in + guard let self = self else { return nil } + return self.menu + } + } + + public override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willEndFor configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating?) { + sendMenuClose() } - func setup () { - let menu = UIMenu(title:_title, identifier: nil, children: self._actions) + let menu = UIMenu(title: _title, + identifier: nil, + children: self._actions) if self._themeVariant != nil { if self._themeVariant == "dark" { @@ -76,15 +91,14 @@ public class MenuViewImplementation: UIButton { } public override func reactSetFrame(_ frame: CGRect) { - super.reactSetFrame(frame); - }; + super.reactSetFrame(frame); + } public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { if hitSlop == .zero || !self.isEnabled || self.isHidden { return super.point(inside: point, with: event) } - // Create a larger hit frame that extends beyond the view's bounds let largerFrame = CGRect( x: self.bounds.origin.x - hitSlop.left, y: self.bounds.origin.y - hitSlop.top, @@ -92,16 +106,22 @@ public class MenuViewImplementation: UIButton { height: self.bounds.size.height + hitSlop.top + hitSlop.bottom ) - // Check if the point is within the larger frame return largerFrame.contains(point) } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } @objc func sendButtonAction(_ action: UIAction) { // NO-OP (should be overriden by parent) } + + @objc func sendMenuClose() { + // NO-OP (should be overriden by parent) + } + + @objc func sendMenuOpen() { + // NO-OP (should be overriden by parent) + } } diff --git a/src/NativeModuleSpecs/UIMenuNativeComponent.ts b/src/NativeModuleSpecs/UIMenuNativeComponent.ts index 34726504..e6509355 100644 --- a/src/NativeModuleSpecs/UIMenuNativeComponent.ts +++ b/src/NativeModuleSpecs/UIMenuNativeComponent.ts @@ -12,6 +12,7 @@ import codegenNativeComponent from "react-native/Libraries/Utilities/codegenNati OR with extending types in an interface, so for now we'll just keep some duplicate types here, to avoid issues while `pod install` takes place. */ + type SubAction = { id?: string; title: string; @@ -45,6 +46,8 @@ type MenuAction = { }; export interface NativeProps extends ViewProps { onPressAction?: DirectEventHandler<{ event: string }>; + onCloseMenu?: DirectEventHandler<{ event: string }>; + onOpenMenu?: DirectEventHandler<{ event: string }>; actions: Array; actionsHash: string; // just a workaround to make sure we don't have to manually compare MenuActions manually in C++ (since it's a struct and that's a pain) title?: string; @@ -55,7 +58,7 @@ export interface NativeProps extends ViewProps { bottom: Int32; left: Int32; right: Int32; - } + }; } export default codegenNativeComponent( diff --git a/src/index.tsx b/src/index.tsx index 9ca58dda..a5362981 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -3,50 +3,50 @@ import { processColor } from "react-native"; import UIMenuView from "./UIMenuView"; import type { - MenuComponentProps, - MenuAction, - ProcessedMenuAction, - NativeActionEvent, - MenuComponentRef, + MenuComponentProps, + MenuAction, + ProcessedMenuAction, + NativeActionEvent, + MenuComponentRef, } from "./types"; import { objectHash } from "./utils"; function processAction(action: MenuAction): ProcessedMenuAction { - return { - ...action, - imageColor: processColor(action.imageColor), - titleColor: processColor(action.titleColor), - subactions: action.subactions?.map((subAction) => processAction(subAction)), - }; + return { + ...action, + imageColor: processColor(action.imageColor), + titleColor: processColor(action.titleColor), + subactions: action.subactions?.map((subAction) => processAction(subAction)), + }; } const defaultHitslop = { top: 0, left: 0, bottom: 0, right: 0 }; const MenuView = forwardRef( - ({ actions, hitSlop = defaultHitslop, ...props }, ref) => { - const processedActions = actions.map((action) => - processAction(action), - ); - const hash = useMemo(() => { - return objectHash(processedActions); - }, [processedActions]); + ({ actions, hitSlop = defaultHitslop, ...props }, ref) => { + const processedActions = actions.map((action) => + processAction(action), + ); + const hash = useMemo(() => { + return objectHash(processedActions); + }, [processedActions]); - return ( - - ); - }, + return ( + + ); + }, ); export { MenuView }; export type { - MenuComponentProps, - MenuComponentRef, - MenuAction, - NativeActionEvent, + MenuComponentProps, + MenuComponentRef, + MenuAction, + NativeActionEvent, }; diff --git a/src/types.ts b/src/types.ts index 6f9dc310..9c323d9c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -115,6 +115,14 @@ type MenuComponentPropsBase = { * It will contain id of the given action. */ onPressAction?: ({ nativeEvent }: NativeActionEvent) => void; + /** + * Callback function that will be called when the menu closes. + */ + onCloseMenu?: () => void; + /** + * Callback function that will be called when the menu opens. + */ + onOpenMenu?: () => void; /** * Actions to be displayed in the menu. */ @@ -179,6 +187,8 @@ export type ProcessedMenuAction = Omit< export type NativeMenuComponentProps = { style?: StyleProp; onPressAction?: ({ nativeEvent }: NativeActionEvent) => void; + onCloseMenu?: () => void; + onOpenMenu?: () => void; actions: ProcessedMenuAction[]; actionsHash: string; title?: string;