Skip to content

Commit 85e43d1

Browse files
committed
wip
1 parent d8016b1 commit 85e43d1

File tree

6 files changed

+97
-10
lines changed

6 files changed

+97
-10
lines changed

src/components/grid/grid.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ export class Grid extends Component<Props, SpreadsheetChildEnv> {
207207

208208
usePinchToZoom(this.gridRef, (scale: number) => {
209209
this.env.model.dispatch("SET_ZOOM", {
210-
zoom: this.env.model.getters.getViewportZoomLevel() * scale,
210+
zoom: Math.round(this.env.model.getters.getViewportZoomLevel() * scale * 100) / 100,
211211
});
212212
});
213213
}

src/components/helpers/pinch_to_zoom_hook.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { Ref } from "@odoo/o-spreadsheet-engine";
22
import { useRefListener } from "./listener_hook";
33

4-
const evCache: PointerEvent[] = [];
5-
let prevDiff = -1;
6-
74
/** Largely inspired by the pinch-to-zoom example provided at MDN
85
* https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events/Pinch_zoom_gestures#example
96
* on the 30th of October 2025
107
*/
118
export function usePinchToZoom(ref: Ref<HTMLElement>, onZoom: (scale: number) => void) {
9+
const evCache: PointerEvent[] = [];
10+
let prevDiff = -1;
11+
1212
useRefListener(ref, "pointerdown", pointerdownHandler, { passive: false });
1313
useRefListener(ref, "pointermove", pointermoveHandler, { passive: false });
1414
useRefListener(ref, "pointerup", pointerupHandler, { passive: false });
@@ -27,23 +27,33 @@ export function usePinchToZoom(ref: Ref<HTMLElement>, onZoom: (scale: number) =>
2727
// The pointerdown event signals the start of a touch interaction.
2828
// This event is cached to support 2-finger gestures
2929
evCache.push(ev);
30+
if (evCache.length < 2) {
31+
prevDiff = -1;
32+
} else if (evCache.length === 2) {
33+
prevDiff = computeDistance(evCache[0], evCache[1]);
34+
}
35+
}
36+
37+
function computeDistance(ev1: PointerEvent, ev2: PointerEvent) {
38+
const dx = ev1.clientX - ev2.clientX;
39+
const dy = ev1.clientY - ev2.clientY;
40+
return Math.sqrt(dx * dx + dy * dy);
3041
}
3142

3243
function pointermoveHandler(ev: PointerEvent) {
3344
// Find this event in the cache and update its record with this event
3445
const index = evCache.findIndex((cachedEv) => cachedEv.pointerId === ev.pointerId);
46+
if (index === -1) return;
47+
3548
evCache[index] = ev;
3649

3750
// If two pointers are down, check for pinch gestures
3851
if (evCache.length === 2) {
3952
// Calculate the distance between the two pointers
40-
const dx = evCache[0].clientX - evCache[1].clientX;
41-
const dy = evCache[0].clientY - evCache[1].clientY;
42-
43-
const curDiff = Math.abs(dx * dx + dy * dy);
53+
const curDiff = computeDistance(evCache[0], evCache[1]);
4454

4555
if (prevDiff > 0) {
46-
onZoom((curDiff / prevDiff) ** 0.25);
56+
onZoom((curDiff / prevDiff) ** 0.5);
4757
}
4858

4959
// Cache the distance for the next move event

src/components/highlight/corner/corner.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<t t-name="o-spreadsheet-Corner">
33
<div
44
class="o-corner d-flex justify-content-center align-items-center"
5-
t-on-pointerdown.prevent="onMouseDown"
5+
t-on-pointerdown.prevent.stop="onMouseDown"
66
t-on-touchstart.prevent.stop=""
77
t-att-style="handlerStyle">
88
<div

tests/grid/grid_component.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ import {
7575
scrollGrid,
7676
simulateClick,
7777
triggerMouseEvent,
78+
triggerPointerEvent,
7879
triggerTouchEvent,
7980
triggerWheelEvent,
8081
} from "../test_helpers/dom_helper";
@@ -2261,3 +2262,43 @@ describe("Header grouping shortcuts", () => {
22612262
});
22622263
});
22632264
});
2265+
test("Can pinch to zoom in", async () => {
2266+
({ parent, model, fixture } = await mountSpreadsheet());
2267+
2268+
const grid = fixture.querySelector(".o-grid-overlay")!;
2269+
const moveDistance = 30;
2270+
triggerPointerEvent(grid, "pointerdown", 100, 100, { pointerId: 1, bubbles: true });
2271+
triggerPointerEvent(grid, "pointerdown", 120, 120, { pointerId: 2, bubbles: true });
2272+
2273+
// zoom in 30 px of distance
2274+
triggerPointerEvent(grid, "pointermove", 120 + moveDistance, 120 + moveDistance, {
2275+
pointerId: 2,
2276+
bubbles: true,
2277+
});
2278+
2279+
await nextTick();
2280+
expect(model.getters.getViewportZoomLevel()).toBe(1.58);
2281+
2282+
// go back to initial
2283+
triggerPointerEvent(grid, "pointermove", 120, 120, {
2284+
pointerId: 2,
2285+
bubbles: true,
2286+
});
2287+
await nextTick();
2288+
expect(model.getters.getViewportZoomLevel()).toBe(1);
2289+
2290+
// // pinch to zoom out - closer pointers
2291+
triggerPointerEvent(grid, "pointermove", 110, 110, {
2292+
pointerId: 1,
2293+
bubbles: true,
2294+
});
2295+
await nextTick();
2296+
expect(model.getters.getViewportZoomLevel()).toBe(0.71);
2297+
2298+
// go back to initial
2299+
triggerPointerEvent(grid, "pointermove", 100, 100, {
2300+
pointerId: 1,
2301+
bubbles: true,
2302+
});
2303+
expect(model.getters.getViewportZoomLevel()).toBe(1);
2304+
});

tests/setup/jest.setup.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ function registerOwlTemplates() {
5454
}
5555
}
5656

57+
class PointerEventPolyfill extends MouseEvent {
58+
pointerId: number;
59+
constructor(type: string, eventInitDict: PointerEventInit) {
60+
super(type, eventInitDict);
61+
this.pointerId = eventInitDict.pointerId ?? 0;
62+
}
63+
}
64+
5765
beforeAll(() => {
5866
registerOwlTemplates();
5967
setDefaultSheetViewSize(1000);
@@ -82,6 +90,9 @@ beforeAll(() => {
8290
};
8391

8492
console.debug = () => {};
93+
94+
// @ts-ignore
95+
window.PointerEvent = PointerEventPolyfill;
8596
});
8697

8798
beforeEach(() => {

tests/test_helpers/dom_helper.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,31 @@ export function triggerKeyboardEvent(
299299
dispatchEvent(selector, ev);
300300
}
301301

302+
export function triggerPointerEvent(
303+
selector: DOMTarget,
304+
type: string,
305+
offsetX?: number,
306+
offsetY?: number,
307+
extra: PointerEventInit = { bubbles: true }
308+
) {
309+
if (type === "pointermove") {
310+
extra = { button: -1, ...extra };
311+
}
312+
const ev = new PointerEvent(type, {
313+
// this is only correct if we assume the target is positioned
314+
// at the very top left corner of the screen
315+
clientX: offsetX,
316+
clientY: offsetY,
317+
bubbles: true,
318+
...extra,
319+
});
320+
(ev as any).offsetX = offsetX;
321+
(ev as any).offsetY = offsetY;
322+
const target = getTarget(selector);
323+
target.dispatchEvent(ev);
324+
return ev;
325+
}
326+
302327
function dispatchEvent(selector: string | EventTarget, ev: Event) {
303328
if (typeof selector === "string") {
304329
const el = document.querySelector(selector);

0 commit comments

Comments
 (0)