@@ -12,7 +12,7 @@ import cx from 'clsx';
1212import { ItemTrackerProvider } from './ItemTrackerProvider' ;
1313
1414import { eventManager } from '../core/eventManager' ;
15- import { TriggerEvent , MenuId , MenuAnimation , Theme } from '../types' ;
15+ import { TriggerEvent , MenuId , MenuAnimation , Theme , MenuOnShowCallback , MenuOnHideCallback , StyleMargin , StyleMarginObject } from '../types' ;
1616import { useItemTracker } from '../hooks' ;
1717import { createKeyboardController } from './keyboardController' ;
1818import { CssClass , EVENT , hideOnEvents } from '../constants' ;
@@ -41,6 +41,14 @@ export interface MenuProps
4141 */
4242 theme ?: Theme ;
4343
44+ /**
45+ * Apply a margin to the viewport for boundary detection. If a context menu
46+ * would appear within this viewport margin it counts as being outside the
47+ * viewport and moves within the margin limits.
48+ * This only works if `disableBoundariesCheck` is not true.
49+ */
50+ viewportMargin ?: StyleMargin ;
51+
4452 /**
4553 * Animation is appended to
4654 * - `.contexify_willEnter-${given animation}`
@@ -85,15 +93,16 @@ export interface MenuProps
8593 * Triggers when a show event is triggered. This triggers even if the menu
8694 * is already shown.
8795 * @param fromHidden - True if the menu was previously hidden.
96+ * @param position - An object of `{x: number, y:number}` for the position the
97+ * context menu is appearing.
8898 */
89- onShow ?: ( fromHidden : boolean ) => void ;
90-
99+ onShow ?: MenuOnShowCallback ;
91100 /**
92101 * Triggers when a hide event is triggered. This triggers even if the menu
93102 * is already hidden.
94103 * @param fromVisible - True if the menu was previously visible.
95104 */
96- onHide ?: ( fromVisible : boolean ) => void ;
105+ onHide ?: MenuOnHideCallback ;
97106}
98107
99108interface MenuState {
@@ -122,6 +131,7 @@ export const Menu = ({
122131 animation = 'fade' ,
123132 preventDefaultOnKeydown = true ,
124133 disableBoundariesCheck = false ,
134+ viewportMargin = 0 ,
125135 onShow,
126136 onHide,
127137 onVisibilityChange,
@@ -140,12 +150,14 @@ export const Menu = ({
140150 const [ menuController ] = useState ( ( ) => createKeyboardController ( ) ) ;
141151
142152 const wasVisible = useRef < boolean > ( false ) ;
153+ const viewPortMargin = useRef < StyleMarginObject > ( typeof viewportMargin === 'number' ? { bottom : viewportMargin , top : viewportMargin , left : viewportMargin , right : viewportMargin } : viewportMargin )
143154
144155 // @deprecated -- NOTE: this is to keep backwards compatibility for onVisibilityChange
145156 const wasVisibleDeprecated = useRef < boolean > ( false ) ;
146157 // @deprecated
147158 const visibilityId = useRef < number > ( 0 ) ;
148159
160+
149161 // allows the caller to assign their own ref, which is then synced with nodeRef
150162 useImperativeHandle ( ref , ( ) => nodeRef . current as HTMLDivElement ) ;
151163
@@ -169,9 +181,14 @@ export const Menu = ({
169181 const { innerWidth, innerHeight } = window ;
170182 const { offsetWidth, offsetHeight } = nodeRef . current ;
171183
172- if ( x + offsetWidth > innerWidth ) x -= x + offsetWidth - innerWidth ;
184+ const xMax = innerWidth - ( viewPortMargin . current ?. right ?? 0 ) ;
185+ // const xMin = viewPortMargin.current.left;
186+ const yMax = innerHeight - ( viewPortMargin . current ?. bottom ?? 0 ) ;
187+ // const yMin = viewPortMargin.current.top;
188+
189+ if ( x + offsetWidth > xMax ) x -= x + offsetWidth - xMax ;
173190
174- if ( y + offsetHeight > innerHeight ) y -= y + offsetHeight - innerHeight ;
191+ if ( y + offsetHeight > yMax ) y -= y + offsetHeight - yMax ;
175192 }
176193
177194 return { x, y } ;
@@ -253,7 +270,7 @@ export const Menu = ({
253270
254271
255272 if ( isFn ( onShow ) ) {
256- onShow ( ! wasVisible . current ) ;
273+ onShow ( ! wasVisible . current , { x , y } ) ;
257274 }
258275
259276 if ( ! wasVisible . current ) {
0 commit comments