/** * ContextMenu — the actions that belong to a piece of content, opened on it. * * A `Menu` hangs off a control that exists to be pressed: a ⋯ button, a toolbar * item, something whose whole job is to open the menu. A context menu has no * such control. The target is the content itself — a message, a note, a photo, * a row — and the actions are reached by holding it, by a named accessibility * action, or from the keyboard. * * ```tsx * * * Would you like an interactive todo list? * * * }>Share * }>Copy * * }> * Report * * * * ``` * * ## It is a Menu, and deliberately so * * The rows here *are* `Menu`'s rows — the same components, not a second set * styled to match. `ContextMenu.Item` and `Menu.Item` are one implementation, * so the destructive colour, the press-in scale, the indicator column and the * dismiss-on-select rule cannot drift apart between the two ways of reaching * them. The panel is `Menu`'s panel, which is `Popover`'s, so `presentation`, * submenus and edge-flipping all arrive already working. * * What this component owns is what a menu opened on content needs and a menu * opened from a button does not: alternate invocation paths, and where the * panel goes. * * ## Anchored to the finger, not to the target * * A toolbar menu is placed against its trigger, because the trigger is small * and its position is the only sensible answer. A context menu's target is * often most of the screen — a whole message, a whole card — and the middle of * it is not where the finger was. So the anchor is the press point by default, * and the panel unfolds from it the way a popover unfolds from a button. * * `anchor="target"` places it against the target's bounds instead, which is the * better answer for something small and list-shaped, where the panel lining up * with the row reads as belonging to it. * * ## Why the gesture is not a Pressable * * The target usually has a press of its own — open the thread, play the video, * follow the link — and the two must not both fire. React Native's `Pressable` * decides between them after the fact, and the tap can still get through on the * way to a long press; the recogniser here is asked for the arbitration up * front instead, so a hold that opens the menu never also counts as a press. * * It is also what lets the target be anything at all. A cloned `onLongPress` * needs a child that takes one, which rules out exactly the plain views — * bubbles, cards, images — that content-native actions are usually attached to. */ import { type ReactNode } from 'react'; import { type ViewProps } from 'react-native'; import { type MenuCheckboxItemProps, type MenuContentProps, type MenuItemProps, type MenuProps } from '../menu/index.js'; import { type ContextMenuKeyDownEvent } from './context-menu-invocation.js'; /** * The root takes exactly what `Menu`'s root takes — `open`, `onOpenChange`, * `defaultOpen`, `presentation` and `haptics` — because it *is* that root. */ export type ContextMenuProps = MenuProps; /** * The root. Provides the menu's own context and the popover underneath it. * * It renders a `Menu`, which is not a shortcut — it is the point. Everything a * menu is, this is, and the parts below are the only difference. */ declare function ContextMenuRoot({ children, ...props }: ContextMenuProps): import("react").JSX.Element; /** Which rectangle the panel is placed against. */ export type ContextMenuAnchor = 'point' | 'target'; export interface ContextMenuTriggerProps extends Omit { /** * Classes on the wrapper the content sits in, which lays out like any other * view — it does not shrink to its child, because the things held are usually * meant to fill their place in the layout. It is also the rect * `anchor="target"` measures. */ className?: string; /** * The content the actions belong to. Anything at all — it is not required to * be pressable, and is not cloned or altered. */ children: ReactNode; /** * `point` anchors the panel where the finger landed, `target` against the * bounds of the whole trigger. * * Point is the default because a context menu's target is usually large, and * the middle of a whole message is not where the press was. Reach for * `target` when the target is small and list-shaped and the panel should read * as lining up with it. Keyboard and accessibility opens always use the target * bounds, because those modalities have no pointer coordinate. */ anchor?: ContextMenuAnchor; /** How long the hold has to last, in milliseconds. 350 by default. */ delay?: number; /** * How far the finger may move during the hold before it stops being one, in * points. 12 by default. * * Loose rather than tight, because the target is usually inside a scroller: a * threshold small enough to feel precise cancels the menu for anyone whose * thumb drifts while holding still, and a scroll has travelled much further * than this by the time the two need telling apart. Tighten it only for a * target that cannot be scrolled. */ slop?: number; /** A short press on the target, which the hold never also counts as. */ onPress?: () => void; /** * Tick the haptic engine as the menu opens. Needs the optional * `expo-haptics`, and is silent without it. * * Worth setting more often than not. A hold has no edge to it the way a press * does — nothing moves under the finger at the moment it takes — so the tick * is what tells someone the hold has been long enough, before the panel has * had time to say so. */ haptics?: boolean; /** Nothing opens the menu, and the short press stops firing too. */ disabled?: boolean; /** * Called first for keyboard events. Prevent the event to keep ContextMenu * from handling it. Context Menu and Shift+F10 open the menu; Enter and Space * mirror the trigger's accessible activation. */ onKeyDown?: (event: ContextMenuKeyDownEvent) => void; } /** * Wraps the content and opens the menu when held, through accessibility * actions, or from the keyboard. * * The wrapper is a plain view and lays out like one, stretching as a view does * rather than shrinking to its child. That is the opposite of what a tooltip's * trigger wants, and for the opposite reason: a tooltip names a control and * belongs over it, while the things held here — a bubble, a card, a row — are * usually meant to fill their place in the layout, and a wrapper that collapsed * around them would change it. * * It is also the rect measured under `anchor="target"`, which is why that * anchoring lines the panel up with the row rather than with the text in it. */ declare function ContextMenuTrigger({ className, children, anchor, delay, slop, onPress, haptics, disabled, accessible, accessibilityActions, onAccessibilityAction, accessibilityRole, accessibilityState, focusable, tabIndex, onKeyDown, ...props }: ContextMenuTriggerProps): import("react").JSX.Element; export interface ContextMenuPreviewProps { /** * Drawn instead of the target itself. For a target that would be wrong to * repeat — one carrying a video, a live map, a text field with a cursor in * it — or one that should show more of itself once it has the screen. * * Left out, the target is drawn again as it stands, which is what makes the * lift read as the content coming forward rather than as a picture of it * appearing. */ children?: ReactNode; /** Extra classes on the lifted copy. */ className?: string; } /** * The target, lifted off the page while its actions are up. * * Declared inside `ContextMenu.Content`, but not drawn there — it floats over * the dimmed screen at the place the target was measured, and the panel is * anchored to that same rectangle so the two never overlap. Its presence is * what switches the anchor: a panel placed at the press point would open across * the very content the preview exists to hold up. * * What it draws is the trigger's own children, rendered a second time. That * keeps the lift honest — it is the content itself coming forward, at the size * and in the place it already occupied — and it is why a target that should not * simply be repeated can pass its own `children` instead. * * It takes no touches. The actions are in the panel; the lifted content is * there to say what they are about, and a second live copy of a pressable card * would be a second place to press. */ declare function ContextMenuPreview({ children, className }: ContextMenuPreviewProps): import("react").JSX.Element | null; declare namespace ContextMenuPreview { var displayName: string; } export interface ContextMenuItemProps extends MenuItemProps { /** * The row's glyph, drawn at the trailing edge rather than in front of the * label. Painted to match the label unless it carries a colour of its own. */ icon?: ReactNode; } /** * One row: the verb at the leading edge, its glyph at the trailing one. * * The other way round is right for a menu dropped from a button, where the * glyphs form a column the eye runs down to find the row it wants. A context * menu is not read that way. It appears under the hand that opened it, already * over the content, and what is being scanned is the *words* — so the words * start at the edge, flush with one another, and the glyph sits at the far side * confirming the row rather than introducing it. * * It is still `Menu.Item` underneath, handed the glyph as its trailing slot. So * the press-in fill, the destructive colour and the dismiss-on-select rule are * one implementation shared with `Menu`, and only the arrangement differs. */ declare function ContextMenuItem({ className, icon, variant, ...props }: ContextMenuItemProps): import("react").JSX.Element; declare namespace ContextMenuItem { var displayName: string; } /** * A row carrying a state. Its tick stays at the leading edge, where `Menu` puts * it, because a tick is not a glyph naming the row — it is the answer to it, * and a column of them is what makes a set of choices readable as one. */ declare function ContextMenuCheckboxItem({ className, ...props }: MenuCheckboxItemProps): import("react").JSX.Element; declare namespace ContextMenuCheckboxItem { var displayName: string; } /** * Everything `Menu.Content` takes. The four listed here are the ones whose * defaults differ, and they are listed so the difference is visible. */ export interface ContextMenuContentProps extends MenuContentProps { /** Which side of the anchor the panel opens on. Down from the press, flipping * above it near the bottom of the screen. */ placement?: MenuContentProps['placement']; /** Where it sits along the other axis. From the press, not centred on it. */ align?: MenuContentProps['align']; /** Gap between the anchor and the panel. Small, so it reads as coming out of * the press rather than floating near it. */ offset?: number; /** * Floor for the panel's width. A context menu has no trigger to take its * width from, and a column of one-word verbs is too narrow to aim at. */ minWidth?: number; /** Dim the screen behind the panel. On here, unlike a plain popover. */ scrim?: boolean; } /** * The panel, with the defaults a context menu wants rather than a popover's. * * It unfolds down and from the press rather than being centred on it — * centring is right for a panel under a button, and wrong for one at a * fingertip, where it would put half the panel back under the hand that opened * it. Near the bottom of the screen `Popover` flips it above the press and * clamps it into the safe area, so the one case where down does not work * answers itself. * * The gap to the anchor is small, so the panel reads as coming out of the press * rather than floating near it. * * The screen dims behind it, which a popover does not do. A context menu is * modal in practice — the content underneath is what the actions are *about*, * so nothing else on the screen is available while it is up, and the dim is * what says so. */ declare function ContextMenuContent({ placement, align, offset, minWidth, scrim, children, ...props }: ContextMenuContentProps): import("react").JSX.Element; export declare const ContextMenu: typeof ContextMenuRoot & { Trigger: typeof ContextMenuTrigger; Content: typeof ContextMenuContent; Preview: typeof ContextMenuPreview; Background: typeof import("../menu/index.js").MenuBackground; Label: typeof import("../menu/index.js").MenuLabel; Item: typeof ContextMenuItem; CheckboxItem: typeof ContextMenuCheckboxItem; RadioGroup: typeof import("../menu/index.js").MenuRadioGroup; RadioItem: typeof import("../menu/index.js").MenuRadioItem; Separator: typeof import("../menu/index.js").MenuSeparator; Sub: typeof import("../menu/index.js").MenuSub; SubTrigger: typeof import("../menu/index.js").MenuSubTrigger; SubContent: typeof import("../menu/index.js").MenuSubContent; }; export {}; //# sourceMappingURL=index.d.ts.map