chore: Replace message menu with context menu
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Calculates the optimal position for a popup element to ensure it remains within viewport bounds
|
||||
*
|
||||
* @param {number} x - The initial x-coordinate (left position) for the element
|
||||
* @param {number} y - The initial y-coordinate (top position) for the element
|
||||
* @param {number} menuW - The width of the element to position
|
||||
* @param {number} menuH - The height of the element to position
|
||||
* @param {number} windowW - The width of the viewport
|
||||
* @param {number} windowH - The height of the viewport
|
||||
* @param {number} [padding=16] - Minimum padding from viewport edges (in pixels)
|
||||
* @returns {Object} An object containing the adjusted { left, top } coordinates
|
||||
*/
|
||||
export const calculatePosition = (
|
||||
x,
|
||||
y,
|
||||
menuW,
|
||||
menuH,
|
||||
windowW,
|
||||
windowH,
|
||||
padding = 16
|
||||
) => {
|
||||
// Initial position
|
||||
let left = x;
|
||||
let top = y;
|
||||
|
||||
// Boundary checks
|
||||
const isOverflowingRight = left + menuW > windowW - padding;
|
||||
const isOverflowingBottom = top + menuH > windowH - padding;
|
||||
|
||||
// Adjust position if overflowing
|
||||
if (isOverflowingRight) left = windowW - menuW - padding;
|
||||
if (isOverflowingBottom) top = windowH - menuH - padding;
|
||||
|
||||
return {
|
||||
left: Math.max(padding, left),
|
||||
top: Math.max(padding, top),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,183 @@
|
||||
import { calculatePosition } from '../position';
|
||||
|
||||
describe('calculatePosition', () => {
|
||||
const WINDOW_WIDTH = 1024;
|
||||
const WINDOW_HEIGHT = 768;
|
||||
const MENU_WIDTH = 400;
|
||||
const MENU_HEIGHT = 300;
|
||||
const DEFAULT_PADDING = 16;
|
||||
|
||||
test('returns the original position when element fits within viewport', () => {
|
||||
const x = 100;
|
||||
const y = 100;
|
||||
|
||||
const result = calculatePosition(
|
||||
x,
|
||||
y,
|
||||
MENU_WIDTH,
|
||||
MENU_HEIGHT,
|
||||
WINDOW_WIDTH,
|
||||
WINDOW_HEIGHT
|
||||
);
|
||||
|
||||
expect(result).toEqual({ left: x, top: y });
|
||||
});
|
||||
|
||||
test('adjusts position when overflowing right edge', () => {
|
||||
const x = WINDOW_WIDTH - 100; // Too far right
|
||||
const y = 100;
|
||||
|
||||
const result = calculatePosition(
|
||||
x,
|
||||
y,
|
||||
MENU_WIDTH,
|
||||
MENU_HEIGHT,
|
||||
WINDOW_WIDTH,
|
||||
WINDOW_HEIGHT
|
||||
);
|
||||
|
||||
const expectedLeft = WINDOW_WIDTH - MENU_WIDTH - DEFAULT_PADDING;
|
||||
expect(result).toEqual({ left: expectedLeft, top: y });
|
||||
});
|
||||
|
||||
test('adjusts position when overflowing bottom edge', () => {
|
||||
const x = 100;
|
||||
const y = WINDOW_HEIGHT - 100; // Too far down
|
||||
|
||||
const result = calculatePosition(
|
||||
x,
|
||||
y,
|
||||
MENU_WIDTH,
|
||||
MENU_HEIGHT,
|
||||
WINDOW_WIDTH,
|
||||
WINDOW_HEIGHT
|
||||
);
|
||||
|
||||
const expectedTop = WINDOW_HEIGHT - MENU_HEIGHT - DEFAULT_PADDING;
|
||||
expect(result).toEqual({ left: x, top: expectedTop });
|
||||
});
|
||||
|
||||
test('adjusts position when overflowing both right and bottom edges', () => {
|
||||
const x = WINDOW_WIDTH - 100; // Too far right
|
||||
const y = WINDOW_HEIGHT - 100; // Too far down
|
||||
|
||||
const result = calculatePosition(
|
||||
x,
|
||||
y,
|
||||
MENU_WIDTH,
|
||||
MENU_HEIGHT,
|
||||
WINDOW_WIDTH,
|
||||
WINDOW_HEIGHT
|
||||
);
|
||||
|
||||
const expectedLeft = WINDOW_WIDTH - MENU_WIDTH - DEFAULT_PADDING;
|
||||
const expectedTop = WINDOW_HEIGHT - MENU_HEIGHT - DEFAULT_PADDING;
|
||||
expect(result).toEqual({ left: expectedLeft, top: expectedTop });
|
||||
});
|
||||
|
||||
test('ensures minimum padding from left edge', () => {
|
||||
const x = -50; // Position would be off-screen to the left
|
||||
const y = 100;
|
||||
|
||||
const result = calculatePosition(
|
||||
x,
|
||||
y,
|
||||
MENU_WIDTH,
|
||||
MENU_HEIGHT,
|
||||
WINDOW_WIDTH,
|
||||
WINDOW_HEIGHT
|
||||
);
|
||||
|
||||
expect(result).toEqual({ left: DEFAULT_PADDING, top: y });
|
||||
});
|
||||
|
||||
test('ensures minimum padding from top edge', () => {
|
||||
const x = 100;
|
||||
const y = -50; // Position would be off-screen at the top
|
||||
|
||||
const result = calculatePosition(
|
||||
x,
|
||||
y,
|
||||
MENU_WIDTH,
|
||||
MENU_HEIGHT,
|
||||
WINDOW_WIDTH,
|
||||
WINDOW_HEIGHT
|
||||
);
|
||||
|
||||
expect(result).toEqual({ left: x, top: DEFAULT_PADDING });
|
||||
});
|
||||
|
||||
test('handles case when element is larger than viewport width', () => {
|
||||
const x = 100;
|
||||
const y = 100;
|
||||
const largeWidth = WINDOW_WIDTH + 200; // Wider than window
|
||||
|
||||
const result = calculatePosition(
|
||||
x,
|
||||
y,
|
||||
largeWidth,
|
||||
MENU_HEIGHT,
|
||||
WINDOW_WIDTH,
|
||||
WINDOW_HEIGHT
|
||||
);
|
||||
|
||||
// Should position at minimum padding
|
||||
expect(result).toEqual({ left: DEFAULT_PADDING, top: y });
|
||||
});
|
||||
|
||||
test('handles case when element is larger than viewport height', () => {
|
||||
const x = 100;
|
||||
const y = 100;
|
||||
const largeHeight = WINDOW_HEIGHT + 200; // Taller than window
|
||||
|
||||
const result = calculatePosition(
|
||||
x,
|
||||
y,
|
||||
MENU_WIDTH,
|
||||
largeHeight,
|
||||
WINDOW_WIDTH,
|
||||
WINDOW_HEIGHT
|
||||
);
|
||||
|
||||
// Should position at minimum padding
|
||||
expect(result).toEqual({ left: x, top: DEFAULT_PADDING });
|
||||
});
|
||||
|
||||
test('accepts custom padding value', () => {
|
||||
const x = WINDOW_WIDTH - 100;
|
||||
const y = WINDOW_HEIGHT - 100;
|
||||
const customPadding = 32;
|
||||
|
||||
const result = calculatePosition(
|
||||
x,
|
||||
y,
|
||||
MENU_WIDTH,
|
||||
MENU_HEIGHT,
|
||||
WINDOW_WIDTH,
|
||||
WINDOW_HEIGHT,
|
||||
customPadding
|
||||
);
|
||||
|
||||
const expectedLeft = WINDOW_WIDTH - MENU_WIDTH - customPadding;
|
||||
const expectedTop = WINDOW_HEIGHT - MENU_HEIGHT - customPadding;
|
||||
expect(result).toEqual({ left: expectedLeft, top: expectedTop });
|
||||
});
|
||||
|
||||
test('ensures custom minimum padding from edges', () => {
|
||||
const x = -50;
|
||||
const y = -50;
|
||||
const customPadding = 32;
|
||||
|
||||
const result = calculatePosition(
|
||||
x,
|
||||
y,
|
||||
MENU_WIDTH,
|
||||
MENU_HEIGHT,
|
||||
WINDOW_WIDTH,
|
||||
WINDOW_HEIGHT,
|
||||
customPadding
|
||||
);
|
||||
|
||||
expect(result).toEqual({ left: customPadding, top: customPadding });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user