Skip to content

Commit 67437cc

Browse files
authored
Merge pull request #257 from finos/css-zoom
Add geometry correction for CSS `zoom` and `scale`
2 parents 65a8771 + d67b4c3 commit 67437cc

File tree

2 files changed

+166
-5
lines changed

2 files changed

+166
-5
lines changed

src/ts/table.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,7 @@ export class RegularTableViewModel extends RegularTableViewModelBase {
382382
last_cells: CellTuple[],
383383
override_row_height?: number,
384384
): void {
385+
const zoom = this.table.currentCSSZoom ?? 1;
385386
const measurements: Array<{
386387
cell: HTMLElement;
387388
metadata: CellMetadata | undefined;
@@ -399,18 +400,20 @@ export class RegularTableViewModel extends RegularTableViewModelBase {
399400
Math.max(
400401
10,
401402
Math.min(
402-
this._column_sizes.row_height ?? box.height,
403-
box.height,
403+
this._column_sizes.row_height ?? box.height / zoom,
404+
box.height / zoom,
404405
),
405406
);
406407

407408
if (metadata?.size_key !== undefined) {
408-
this._column_sizes.indices[metadata.size_key] = box.width;
409+
this._column_sizes.indices[metadata.size_key] =
410+
box.width / zoom;
409411
if (
410-
box.width &&
412+
box.width / zoom &&
411413
this._column_sizes.override[metadata.size_key] === undefined
412414
) {
413-
this._column_sizes.auto[metadata.size_key] = box.width;
415+
this._column_sizes.auto[metadata.size_key] =
416+
box.width / zoom;
414417
}
415418
}
416419
}

tests/cssZoom.spec.js

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
2+
// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░
3+
// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░
4+
// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░
5+
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
6+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
7+
// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃
8+
// ┃ * of the Regular Table library, distributed under the terms of the * ┃
9+
// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
10+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
11+
12+
import { test, expect } from "@playwright/test";
13+
14+
test.describe("Render behavior with CSS zoom applied", () => {
15+
test.beforeEach(async ({ page }) => {
16+
await page.setViewportSize({ width: 800, height: 600 });
17+
await page.goto("/tests/large_data.html");
18+
await page.waitForSelector("regular-table table tbody tr td");
19+
});
20+
21+
for (const zoom of [0.5, 1.5]) {
22+
test(`renders correct DOM at origin with zoom ${zoom}`, async ({
23+
page,
24+
}) => {
25+
const table = page.locator("regular-table");
26+
27+
// Apply CSS zoom and re-draw
28+
await table.evaluate(async (el, z) => {
29+
el.style.zoom = `${z}`;
30+
await el.draw();
31+
}, zoom);
32+
33+
// Validate DOM structure
34+
const hasTableTag = await page
35+
.locator("regular-table table")
36+
.count();
37+
const hasThead = await page.locator("regular-table thead").count();
38+
const hasTbody = await page.locator("regular-table tbody").count();
39+
40+
expect(hasTableTag).toBe(1);
41+
expect(hasThead).toBe(1);
42+
expect(hasTbody).toBe(1);
43+
44+
// Validate first cell content is at origin
45+
const firstCell = await page
46+
.locator("regular-table tbody tr:first-child td:first-of-type")
47+
.textContent();
48+
expect(firstCell).toBe("0");
49+
50+
// Validate row header shows Row 0
51+
const firstRowHeader = await page
52+
.locator("regular-table tbody tr:first-child th:nth-child(2)")
53+
.textContent();
54+
expect(firstRowHeader).toBe("Row 0");
55+
56+
// Validate column header shows Column 0
57+
const firstColHeader = await page
58+
.locator("regular-table thead tr:nth-child(2) th:nth-child(3)")
59+
.textContent();
60+
expect(firstColHeader).toBe("Column 0");
61+
});
62+
63+
test(`validates all visible cells at origin with zoom ${zoom}`, async ({
64+
page,
65+
}) => {
66+
const table = page.locator("regular-table");
67+
68+
// Apply CSS zoom and re-draw
69+
await table.evaluate(async (el, z) => {
70+
el.style.zoom = `${z}`;
71+
await el.draw();
72+
}, zoom);
73+
74+
// Validate first row cell values (should be column indices: 0, 1, 2, ...)
75+
const firstRowCells = await page
76+
.locator("regular-table tbody tr:first-child")
77+
.evaluate((el) =>
78+
Array.from(el.querySelectorAll("td")).map(
79+
(td) => td.textContent,
80+
),
81+
);
82+
83+
firstRowCells.forEach((cell, idx) => {
84+
expect(cell).toBe(String(idx));
85+
});
86+
87+
// Validate first column cell values (should be row indices: 0, 1, 2, ...)
88+
const allRowsFirstCells = await page
89+
.locator("regular-table tbody tr")
90+
.evaluateAll((rows) =>
91+
rows.map((row) => {
92+
const td = row.querySelector("td");
93+
return td ? td.textContent : null;
94+
}),
95+
);
96+
97+
allRowsFirstCells.forEach((cell, rowIdx) => {
98+
if (cell !== null) {
99+
expect(cell).toBe(String(rowIdx));
100+
}
101+
});
102+
});
103+
104+
test(`renders correctly after scroll traffic and flush with zoom ${zoom}`, async ({
105+
page,
106+
}) => {
107+
const table = page.locator("regular-table");
108+
109+
// Apply CSS zoom
110+
await table.evaluate(async (el, z) => {
111+
el.style.zoom = `${z}`;
112+
await el.draw();
113+
}, zoom);
114+
115+
// Generate heavy scroll traffic
116+
await table.evaluate(async (el) => {
117+
let scrollPositions = [];
118+
for (let i = 0; i < 100; i++) {
119+
scrollPositions.push({
120+
top: Math.round(Math.random() * 50) * 1000,
121+
left: Math.round(Math.random() * 50) * 1000,
122+
});
123+
}
124+
125+
for (const pos of scrollPositions) {
126+
el.scrollTop = pos.top;
127+
el.scrollLeft = pos.left;
128+
await el.flush();
129+
}
130+
});
131+
132+
// Scroll back to origin and flush
133+
await table.evaluate(async (el) => {
134+
el.scrollTop = 0;
135+
el.scrollLeft = 0;
136+
await el.flush();
137+
});
138+
139+
// Validate first cell content is at origin
140+
const firstCell = await page
141+
.locator("regular-table tbody tr:first-child td:first-of-type")
142+
.textContent();
143+
expect(firstCell).toBe("0");
144+
145+
// Validate row header shows Row 0
146+
const firstRowHeader = await page
147+
.locator("regular-table tbody tr:first-child th:nth-child(2)")
148+
.textContent();
149+
expect(firstRowHeader).toBe("Row 0");
150+
151+
// Validate column header shows Column 0
152+
const firstColHeader = await page
153+
.locator("regular-table thead tr:nth-child(2) th:nth-child(3)")
154+
.textContent();
155+
expect(firstColHeader).toBe("Column 0");
156+
});
157+
}
158+
});

0 commit comments

Comments
 (0)