Skip to content
Closed
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
16 changes: 13 additions & 3 deletions src/js/core/tools/Popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,23 @@ export default class Popup extends CoreFeature{
case "bottom":
this.element.style.top = (parseInt(this.element.style.top) - this.element.offsetHeight - parentEl.offsetHeight - 1) + "px";
break;

default:
this.element.style.top = (parseInt(this.element.style.top) - this.element.offsetHeight + parentEl.offsetHeight + 1) + "px";
}

}else{
this.element.style.height = offsetHeight + "px";
let menuHeight = this.element.offsetHeight;
if(menuHeight > offsetHeight){
this.element.style.top = "0px";
this.element.style.height = offsetHeight + "px";
}else{
let newTop = y - menuHeight;
if(newTop < 0){
newTop = offsetHeight - menuHeight;
}
this.element.style.top = newTop + "px";
}
}
}
}
Expand Down
61 changes: 61 additions & 0 deletions test/e2e/menu.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Tabulator Menu Test</title>
<link rel="stylesheet" href="../../dist/css/tabulator.min.css" />
<script src="../../dist/js/tabulator.js"></script>
<style>
html, body {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
font-family: Arial, sans-serif;
}
#test-table {
width: 100vw;
height: 100vh;
}
</style>
</head>
<body>
<div id="test-table"></div>

<script>
document.addEventListener("DOMContentLoaded", function () {
const testData = [];
for (let i = 1; i <= 50; i++) {
testData.push({
id: i,
name: "Person " + i,
age: 20 + (i % 50),
gender: i % 2 ? "Male" : "Female",
});
}

const columns = [
{ title: "ID", field: "id", sorter: "number" },
{ title: "Name", field: "name", sorter: "string" },
{ title: "Age", field: "age", sorter: "number" },
{ title: "Gender", field: "gender", sorter: "string" },
];

const rowContextMenu = [];
for (let i = 1; i <= 12; i++) {
rowContextMenu.push({
label: "Long menu item number " + i,
action: function () {},
});
}

window.testTable = new Tabulator("#test-table", {
data: testData,
columns: columns,
layout: "fitColumns",
rowContextMenu: rowContextMenu,
});
});
</script>
</body>
</html>
52 changes: 52 additions & 0 deletions test/e2e/menu.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// @ts-check
import { test, expect } from "@playwright/test";

test.describe("Context menu viewport bounds", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/test/e2e/menu.html");
await page.waitForSelector(".tabulator-row");
});

const margin = 5;
const positions = [
{ name: "top-left", offsetX: margin, offsetY: 50 },
{ name: "top-right", offsetX: -margin, offsetY: 50 },
{ name: "bottom-left", offsetX: margin, offsetY: -margin },
{ name: "bottom-right", offsetX: -margin, offsetY: -margin },
{ name: "middle", offsetX: 0.5, offsetY: 0.5 },
];

for (const corner of positions) {
test(`menu stays inside viewport when opened near ${corner.name}`, async ({ page }) => {
const viewport = page.viewportSize();
const resolve = (offset, size) => {
if (offset > 0 && offset < 1) return Math.round(size * offset);
return offset < 0 ? size + offset : offset;
};
const x = resolve(corner.offsetX, viewport.width);
const y = resolve(corner.offsetY, viewport.height);

await page.evaluate(({ x, y }) => {
const el = document.elementFromPoint(x, y);
el.dispatchEvent(new MouseEvent("contextmenu", {
bubbles: true,
cancelable: true,
view: window,
button: 2,
clientX: x,
clientY: y,
}));
}, { x, y });

const menu = page.locator(".tabulator-menu");
await expect(menu).toBeVisible();

const box = await menu.boundingBox();
expect(box).not.toBeNull();
expect(box.x).toBeGreaterThanOrEqual(0);
expect(box.y).toBeGreaterThanOrEqual(0);
expect(box.x + box.width).toBeLessThanOrEqual(viewport.width);
expect(box.y + box.height).toBeLessThanOrEqual(viewport.height);
});
}
});
Loading