Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,14 @@ Boolean determining if menu should open after long press or on normal press
|---------|----------|
| boolean | No |

### `onPress`

Callback fired when the anchor is tapped (a normal press, not a long press). Only meaningful together with [`shouldOpenOnLongPress`](#shouldopenonlongpress): the menu opens on long press while a normal tap invokes this — letting the anchor be both tappable and long-pressable (e.g. tap a row to open it, long-press for a context menu).

| Type | Required |
|------------|----------|
| () => void | No |

### `actions`

Actions to be displayed in the menu.
Expand Down
8 changes: 8 additions & 0 deletions android/src/main/java/com/reactnativemenu/MenuView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ class MenuView(private val mContext: ReactContext) : ReactViewGroup(mContext) {
override fun onSingleTapUp(e: MotionEvent): Boolean {
if (!mIsOnLongPress) {
prepareMenu()
} else {
// In long-press mode a normal tap acts as a press, emitted through
// onPressAction with a sentinel id.
val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(mContext, id)
val surfaceId: Int = UIManagerHelper.getSurfaceId(this)
dispatcher?.dispatchEvent(
MenuOnPressActionEvent(surfaceId, id, "rnmenu:onPress", id)
)
}
return true
}
Expand Down
4 changes: 4 additions & 0 deletions ios/Shared/ActionSheetView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ public class ActionSheetView: UIView {

@objc func handleTap(_ sender:UITapGestureRecognizer) {
if shouldOpenOnLongPress {
// Tap acts as a press when the sheet opens on long press.
if sender.state == .ended {
self.sendButtonAction("rnmenu:onPress")
}
return
}
if sender.state == .ended {
Expand Down
13 changes: 13 additions & 0 deletions ios/Shared/MenuViewImplementation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,21 @@ public class MenuViewImplementation: UIButton {
super.init(frame: frame)
let interaction = UIContextMenuInteraction(delegate: self)
self.addInteraction(interaction)
// In long-press mode the menu opens via the context-menu interaction,
// leaving a normal tap free. The UIButton fires touchUpInside on tap;
// forward it through the action callback with a sentinel id.
self.addTarget(self, action: #selector(handlePress), for: .touchUpInside)
self.setup()
}

// Emit a press only when the menu opens on long press (primary action
// disabled); in primary-action mode the tap already opens the menu.
@objc func handlePress() {
if !self.showsMenuAsPrimaryAction {
let action = UIAction(title: "", identifier: UIAction.Identifier("rnmenu:onPress")) { _ in }
self.sendButtonAction(action)
}
}

public override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
sendMenuOpen()
Expand Down
15 changes: 14 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@ function processAction(action: MenuAction): ProcessedMenuAction {

const defaultHitslop = { top: 0, left: 0, bottom: 0, right: 0 };

// Sentinel action id the native side emits through onPressAction on a plain
// tap of the anchor (only when shouldOpenOnLongPress is set). The JS layer
// translates it into the public `onPress` callback so an anchor can be both
// tapped and long-pressed.
const ON_PRESS_EVENT = "rnmenu:onPress";

const MenuView = forwardRef<MenuComponentRef, MenuComponentProps>(
({ actions, hitSlop = defaultHitslop, ...props }, ref) => {
({ actions, hitSlop = defaultHitslop, onPress, onPressAction, ...props }, ref) => {
const processedActions = actions.map<ProcessedMenuAction>((action) =>
processAction(action),
);
Expand All @@ -37,6 +43,13 @@ const MenuView = forwardRef<MenuComponentRef, MenuComponentProps>(
hitSlop={hitSlop}
actions={processedActions}
actionsHash={hash}
onPressAction={(event) => {
if (event.nativeEvent.event === ON_PRESS_EVENT) {
onPress?.();
return;
}
onPressAction?.(event);
}}
ref={ref}
/>
);
Expand Down
7 changes: 7 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ type MenuComponentPropsBase = {
* It will contain id of the given action.
*/
onPressAction?: ({ nativeEvent }: NativeActionEvent) => void;
/**
* Callback fired when the anchor is tapped (a normal press, not a long
* press). Only meaningful together with `shouldOpenOnLongPress`: the menu
* opens on long press while a normal tap invokes this — letting the anchor
* be both tappable and long-pressable.
*/
onPress?: () => void;
/**
* Callback function that will be called when the menu closes.
*/
Expand Down