Skip to content

Commit 28c9186

Browse files
committed
Forward events from the accessibility layer
- Works by just calling .click() on that cell - Put the accessibility layer behind the canvas so that it doesn't take any pointer events from real clicking
1 parent f6c4380 commit 28c9186

4 files changed

Lines changed: 98 additions & 22 deletions

File tree

packages/grid/src/Grid.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2318,6 +2318,7 @@ class Grid extends PureComponent<GridProps, GridState> {
23182318
<GridAccessibilityLayer
23192319
metrics={metrics}
23202320
model={model}
2321+
canvasRef={this.canvas}
23212322
/>
23222323
);
23232324
}
@@ -2388,6 +2389,7 @@ class Grid extends PureComponent<GridProps, GridState> {
23882389

23892390
return (
23902391
<div className="grid-wrapper" ref={this.canvasWrapper}>
2392+
{this.renderAccessibilityLayer()}
23912393
<canvas
23922394
className={classNames('grid-canvas', Grid.getCursorClassName(cursor))}
23932395
ref={canvas => {
@@ -2406,7 +2408,6 @@ class Grid extends PureComponent<GridProps, GridState> {
24062408
Your browser does not support HTML canvas. Update your browser?
24072409
</canvas>
24082410
{this.renderInputField()}
2409-
{this.renderAccessibilityLayer()}
24102411
{children}
24112412
</div>
24122413
);

packages/grid/src/GridAccessibilityLayer.test.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ function renderAccessibilityLayer(
150150
<GridAccessibilityLayer
151151
metrics={metrics}
152152
model={model}
153+
canvasRef={null}
153154
{...propsOverrides}
154155
/>
155156
);
@@ -251,11 +252,11 @@ describe('GridAccessibilityLayer', () => {
251252
});
252253
});
253254

254-
it('cells have pointer-events: none to allow clicks through to canvas', () => {
255+
it('cells have pointer-events: auto to receive clicks for forwarding', () => {
255256
renderAccessibilityLayer();
256257

257258
const cell = screen.getByTestId('grid-cell-0-0');
258-
expect(cell).toHaveStyle({ pointerEvents: 'none' });
259+
expect(cell).toHaveStyle({ pointerEvents: 'auto' });
259260
});
260261

261262
it('includes aria-rowcount and aria-colcount on the container', () => {

packages/grid/src/GridAccessibilityLayer.tsx

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { type CSSProperties, memo } from 'react';
1+
import React, { type CSSProperties, memo, useCallback } from 'react';
22
import type GridMetrics from './GridMetrics';
33
import type GridModel from './GridModel';
44

@@ -7,6 +7,8 @@ export interface GridAccessibilityLayerProps {
77
metrics: GridMetrics | null;
88
/** The model providing cell data */
99
model: GridModel;
10+
/** Reference to the canvas element for dispatching click events */
11+
canvasRef: HTMLCanvasElement | null;
1012
}
1113

1214
const containerStyle: CSSProperties = {
@@ -20,7 +22,7 @@ const cellStyle: CSSProperties = {
2022
position: 'absolute',
2123
opacity: 0,
2224
overflow: 'hidden',
23-
pointerEvents: 'none',
25+
pointerEvents: 'auto',
2426
};
2527

2628
/**
@@ -33,12 +35,77 @@ const cellStyle: CSSProperties = {
3335
* - Column headers with `data-testid="grid-column-header-{column}-{depth}"`
3436
* - Row headers with `data-testid="grid-row-header-{row}"`
3537
*
36-
* All elements have pointer-events: none so clicks pass through to the underlying canvas.
38+
* Clicks on accessibility elements are forwarded to the underlying canvas at the
39+
* corresponding coordinates, triggering normal grid mouse handling.
3740
*/
3841
function GridAccessibilityLayer({
3942
metrics,
4043
model,
44+
canvasRef,
4145
}: GridAccessibilityLayerProps): JSX.Element | null {
46+
const forwardMouseEvent = useCallback(
47+
(event: React.MouseEvent<HTMLDivElement>, eventType: string) => {
48+
if (!canvasRef) return;
49+
50+
// Get the center of the clicked element
51+
const rect = event.currentTarget.getBoundingClientRect();
52+
const clientX = rect.left + rect.width / 2;
53+
const clientY = rect.top + rect.height / 2;
54+
55+
// Dispatch a synthetic mouse event to the canvas at the cell's position
56+
const syntheticEvent = new MouseEvent(eventType, {
57+
bubbles: true,
58+
cancelable: true,
59+
clientX,
60+
clientY,
61+
button: event.button,
62+
buttons: event.buttons,
63+
ctrlKey: event.ctrlKey,
64+
shiftKey: event.shiftKey,
65+
altKey: event.altKey,
66+
metaKey: event.metaKey,
67+
});
68+
69+
canvasRef.dispatchEvent(syntheticEvent);
70+
},
71+
[canvasRef]
72+
);
73+
74+
const handleClick = useCallback(
75+
(event: React.MouseEvent<HTMLDivElement>) => {
76+
forwardMouseEvent(event, 'click');
77+
},
78+
[forwardMouseEvent]
79+
);
80+
81+
const handleDoubleClick = useCallback(
82+
(event: React.MouseEvent<HTMLDivElement>) => {
83+
forwardMouseEvent(event, 'dblclick');
84+
},
85+
[forwardMouseEvent]
86+
);
87+
88+
const handleContextMenu = useCallback(
89+
(event: React.MouseEvent<HTMLDivElement>) => {
90+
forwardMouseEvent(event, 'contextmenu');
91+
},
92+
[forwardMouseEvent]
93+
);
94+
95+
const handleMouseDown = useCallback(
96+
(event: React.MouseEvent<HTMLDivElement>) => {
97+
forwardMouseEvent(event, 'mousedown');
98+
},
99+
[forwardMouseEvent]
100+
);
101+
102+
const handleMouseUp = useCallback(
103+
(event: React.MouseEvent<HTMLDivElement>) => {
104+
forwardMouseEvent(event, 'mouseup');
105+
},
106+
[forwardMouseEvent]
107+
);
108+
42109
if (!metrics) {
43110
return null;
44111
}
@@ -99,6 +166,11 @@ function GridAccessibilityLayer({
99166
width,
100167
height,
101168
}}
169+
onClick={handleClick}
170+
onDoubleClick={handleDoubleClick}
171+
onContextMenu={handleContextMenu}
172+
onMouseDown={handleMouseDown}
173+
onMouseUp={handleMouseUp}
102174
>
103175
{text}
104176
</div>
@@ -134,6 +206,11 @@ function GridAccessibilityLayer({
134206
width,
135207
height: columnHeaderHeight,
136208
}}
209+
onClick={handleClick}
210+
onDoubleClick={handleDoubleClick}
211+
onContextMenu={handleContextMenu}
212+
onMouseDown={handleMouseDown}
213+
onMouseUp={handleMouseUp}
137214
>
138215
{text ?? ''}
139216
</div>
@@ -167,6 +244,11 @@ function GridAccessibilityLayer({
167244
width: rowHeaderWidth,
168245
height,
169246
}}
247+
onClick={handleClick}
248+
onDoubleClick={handleDoubleClick}
249+
onContextMenu={handleContextMenu}
250+
onMouseDown={handleMouseDown}
251+
onMouseUp={handleMouseUp}
170252
>
171253
{text}
172254
</div>

tests/grid-accessibility.spec.ts

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -84,17 +84,17 @@ test.describe('grid accessibility layer', () => {
8484
await expect(xHeader).toBeAttached();
8585
});
8686

87-
test('accessibility layer does not block canvas interactions', async ({
87+
test('accessibility layer forwards clicks to canvas', async ({
8888
page,
8989
}) => {
9090
const grid = page.locator('.iris-grid-panel .iris-grid');
9191
const canvas = grid.locator('canvas.grid-canvas');
9292

93-
// Click on the grid - should select a cell
94-
await grid.click({ position: { x: 50, y: 50 } });
93+
// Click on an accessibility cell - it should forward the click to the canvas
94+
const cell = page.getByTestId('grid-cell-0-0');
95+
await cell.click();
9596

96-
// The canvas should receive focus, not blocked by the accessibility layer
97-
// The accessibility layer has pointer-events: none
97+
// The canvas should receive focus through the forwarded click
9898
await expect(canvas).toBeFocused();
9999
});
100100

@@ -109,23 +109,15 @@ test.describe('grid accessibility layer', () => {
109109
await expect(rowHeader0).not.toBeAttached();
110110
});
111111

112-
test('can click on third row cell using accessibility layer position', async ({
112+
test('can click on third row cell using accessibility layer', async ({
113113
page,
114114
}) => {
115115
// Get the cell in the third row (row index 2, zero-based)
116116
const thirdRowCell = page.getByTestId('grid-cell-0-2');
117117
await expect(thirdRowCell).toBeAttached();
118118

119-
// Get the bounding box of the accessibility element to find its position
120-
const boundingBox = await thirdRowCell.boundingBox();
121-
expect(boundingBox).not.toBeNull();
122-
if (boundingBox === null) return;
123-
124-
// Click at the center of the cell position on the canvas
125-
await page.mouse.click(
126-
boundingBox.x + boundingBox.width / 2,
127-
boundingBox.y + boundingBox.height / 2
128-
);
119+
// Click directly on the accessibility element - it forwards to the canvas
120+
await thirdRowCell.click();
129121

130122
// Take a screenshot to verify the third row is selected
131123
await expect(page.locator('.iris-grid-panel .iris-grid')).toHaveScreenshot(

0 commit comments

Comments
 (0)