Skip to content

Keep focus in overlay editor #915

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion packages/core/src/cells/number-cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const numberCellRenderer: InternalCellRenderer<NumberCell> = {
<React.Suspense fallback={null}>
<NumberOverlayEditor
highlight={isHighlighted}
disabled={value.readonly === true}
readOnly={value.readonly === true}
value={value.data}
fixedDecimals={value.fixedDecimals}
allowNegative={value.allowNegative}
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/cells/row-id-cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const rowIDCellRenderer: InternalCellRenderer<RowIDCell> = {
<GrowingEntry
highlight={isHighlighted}
autoFocus={value.readonly !== true}
disabled={value.readonly !== false}
readOnly={value.readonly === true}
value={value.data}
validatedSelection={validatedSelection}
onChange={e =>
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/cells/text-cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const textCellRenderer: InternalCellRenderer<TextCell> = {
style={cell.allowWrapping === true ? { padding: "3px 8.5px" } : undefined}
highlight={isHighlighted}
autoFocus={value.readonly !== true}
disabled={value.readonly === true}
readOnly={value.readonly === true}
altNewline={true}
value={value.data}
validatedSelection={validatedSelection}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ export const DataGridOverlayEditorStyle = styled.div<Props>`
0px 6px 12px rgba(62, 65, 86, 0.15);

animation: glide_fade_in 60ms 1;

&:focus {
outline: none;
Copy link
Preview

Copilot AI Jun 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Removing the default focus outline without providing an alternative focus indicator can harm keyboard accessibility. Consider adding a visible focus style to meet accessibility guidelines.

Suggested change
outline: none;
outline: none;
box-shadow: 0 0 0 2px var(--gdg-focus-color, #4D90FE); /* Default to a blue focus ring */

Copilot uses AI. Check for mistakes.

}
}

&.gdg-pad {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,16 +220,23 @@ const DataGridOverlayEditor: React.FunctionComponent<DataGridOverlayEditorProps>
onClickOutside={onClickOutside}
isOutsideClick={isOutsideClick}>
<DataGridOverlayEditorStyle
ref={ref}
ref={elem => {
ref(elem);
if (elem) elem.focus();
}}
Comment on lines +223 to +226
Copy link
Preview

Copilot AI Jun 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Using an inline ref callback to both forward the ref and call elem.focus() may trigger focus on every render. It’s better to forward the ref normally and call focus once in a useEffect on mount.

Suggested change
ref={elem => {
ref(elem);
if (elem) elem.focus();
}}
ref={ref}

Copilot uses AI. Check for mistakes.

id={id}
data-testid="data-grid-overlay-editor"
className={classWrap}
style={styleOverride}
as={useLabel === true ? "label" : undefined}
targetX={target.x - bloomX}
targetY={target.y - bloomY}
targetWidth={target.width + bloomX * 2}
targetHeight={target.height + bloomY * 2}>
<div className="gdg-clip-region" onKeyDown={onKeyDown}>
targetHeight={target.height + bloomY * 2}
tabIndex={-1}
onKeyDown={onKeyDown}
>
<div className="gdg-clip-region">
{editor}
</div>
</DataGridOverlayEditorStyle>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ const BubblesOverlayEditor: React.FunctionComponent<Props> = p => {
{b}
</div>
))}
<textarea className="gdg-input" autoFocus={true} />
</BubblesOverlayEditorStyle>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ export const MarkdownOverlayEditor: React.FunctionComponent<Props> = p => {
</div>
</>
)}
<textarea className="gdg-md-edit-textarea gdg-input" autoFocus={true} />
</MarkdownOverlayEditorStyle>
);
};

export default MarkdownOverlayEditor;
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { NumberFormatValues } from "react-number-format/types/types.js";
interface Props {
readonly value: number | undefined;
readonly disabled?: boolean;
readonly readOnly?: boolean;
readonly onChange: (values: NumberFormatValues) => void;
readonly highlight: boolean;
readonly validatedSelection?: SelectionRange;
Expand Down Expand Up @@ -34,6 +35,7 @@ const NumberOverlayEditor: React.FunctionComponent<Props> = p => {
value,
onChange,
disabled,
readOnly,
highlight,
validatedSelection,
fixedDecimals,
Expand Down Expand Up @@ -61,6 +63,7 @@ const NumberOverlayEditor: React.FunctionComponent<Props> = p => {
e.target.setSelectionRange(highlight ? 0 : e.target.value.length, e.target.value.length)
}
disabled={disabled === true}
readOnly={readOnly === true}
decimalScale={fixedDecimals}
allowNegative={allowNegative}
thousandSeparator={thousandSeparator ?? getThousandSeprator()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ const UriOverlayEditor: React.FunctionComponent<Props> = p => {
<EditPencil />
</div>
)}
<textarea className="gdg-input" autoFocus={true} />
</UriOverlayEditorStyle>
);
};
Expand Down
123 changes: 121 additions & 2 deletions packages/core/test/data-editor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
isSizedGridColumn,
type Item,
} from "../src/index.js";
import type { CustomCell } from "../src/internal/data-grid/data-grid-types.js";
import type { CustomCell, SizedGridColumn } from "../src/internal/data-grid/data-grid-types.js";
import type { DataEditorRef } from "../src/data-editor/data-editor.js";
import { assert } from "../src/common/support.js";
import { vi, type Mock, expect, describe, test, beforeEach, afterEach } from "vitest";
Expand Down Expand Up @@ -1352,7 +1352,7 @@ describe("data-editor", () => {
expect(document.body.contains(overlay)).toBe(false);
});

test("Open markdown overlay", async () => {
test("Open and close markdown overlay", async () => {
vi.useFakeTimers();
render(<DataEditor {...basicProps} />, {
wrapper: Context,
Expand Down Expand Up @@ -1384,6 +1384,125 @@ describe("data-editor", () => {
expect(document.body.contains(overlay)).toBe(false);
});

test("Open and close overlays", async () => {
vi.useFakeTimers();

const columns = basicProps.columns.slice(0, 5);
const getCellContent: (typeof basicProps)["getCellContent"] = c => {
const [col, row] = c;
switch (col) {
case 0: {
return {
kind: GridCellKind.Bubble,
allowOverlay: true,
data: ["Foobar"],
readOnly: true,
};
}
case 1: {
return {
kind: GridCellKind.Drilldown,
allowOverlay: true,
data: [
{
img: "https://cdn.pixabay.com/photo/2017/02/20/18/03/cat-2083492_1280.jpg",
text: "Foobar",
},
],
readOnly: true,
};
}
case 2: {
return {
kind: GridCellKind.Image,
data: [
"https://i.imgur.com/5J0BftG.jpg",
"https://preview.redd.it/7jlqkp2cyap51.jpg?width=575&auto=webp&s=26fa9ed15b16fb450ee08ed1f2f0ccb5e0223581",
],
allowOverlay: true,
readOnly: true,
};
}
case 3: {
return {
kind: GridCellKind.Markdown,
allowOverlay: true,
data: `# Header: ${col}, ${row}`,
readOnly: true,
};
}
case 4: {
return {
kind: GridCellKind.Number,
allowOverlay: true,
data: row,
displayData: row.toString(),
readOnly: true,
};
}
case 5: {
return {
kind: GridCellKind.Uri,
allowOverlay: true,
data: "https://www.google.com",
readOnly: true,
};
}
}

return basicProps.getCellContent(c);
};

render(
<DataEditor
{...basicProps}
columns={columns}
getCellContent={getCellContent}
/>,
{
wrapper: Context,
}
);
prep();

const canvas = screen.getByTestId("data-grid-canvas");

let clientX = 10;
for (const col of columns) {
// Double click to open overlay
sendClick(canvas, {
clientX,
clientY: 36 + 32 + 16, // Row 1 (0 indexed)
});

sendClick(canvas, {
clientX,
clientY: 36 + 32 + 16, // Row 1 (0 indexed)
});

// Single click on overlay editor
sendClick(canvas, {
clientX,
clientY: 36 + 32 + 16, // Row 1 (0 indexed)
});

const overlay = screen.getByTestId("data-grid-overlay-editor")
expect(document.body.contains(overlay)).toBe(true);

vi.useFakeTimers();
fireEvent.keyDown(canvas, {
key: "Escape",
});
act(() => {
vi.runAllTimers();
});

expect(document.body.contains(overlay)).toBe(false);

clientX += (col as SizedGridColumn).width;
}
})

test("Open overlay with keypress", async () => {
vi.useFakeTimers();
render(<DataEditor {...basicProps} />, {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/test/data-grid-overlay.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { render, cleanup } from "@testing-library/react";
import BubblesOverlayEditor from "../src/internal/data-grid-overlay-editor/private/bubbles-overlay-editor.js";
import DrilldownOverlayEditor from "../src/internal/data-grid-overlay-editor/private/drilldown-overlay-editor.js";
import { GridCellKind, ImageOverlayEditor } from "../src/index.js";
import { MarkdownOverlayEditor } from "../src/internal/data-grid-overlay-editor/private/markdown-overlay-editor.js";
import MarkdownOverlayEditor from "../src/internal/data-grid-overlay-editor/private/markdown-overlay-editor.js";
import NumberOverlayEditor from "../src/internal/data-grid-overlay-editor/private/number-overlay-editor.js";
import UriOverlayEditor from "../src/internal/data-grid-overlay-editor/private/uri-overlay-editor.js";
import { vi, describe, test, afterEach } from "vitest";
Expand Down
2 changes: 1 addition & 1 deletion setup-react-18-test.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/bash

npm i -D react@latest react-dom@latest @testing-library/react@latest @testing-library/react-hooks@latest @testing-library/user-event@14.5.1 react-test-renderer@latest
npm i -D react@latest react-dom@latest @testing-library/react@latest @testing-library/react-hooks@latest @testing-library/user-event@14.5.1 react-test-renderer@latest @testing-library/dom