Skip to content

Commit d57a9bf

Browse files
committed
fix(webapp): enforce keyboard interaction safeguards
1 parent beb6bc5 commit d57a9bf

5 files changed

Lines changed: 28 additions & 14 deletions

File tree

.oxlintrc.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
"react/no-unknown-property": "error",
7777
"jsx-a11y/alt-text": "error",
7878
"jsx-a11y/aria-role": "error",
79-
"jsx-a11y/click-events-have-key-events": "off",
79+
"jsx-a11y/click-events-have-key-events": "error",
8080
"jsx-a11y/control-has-associated-label": [
8181
"error",
8282
{
@@ -87,7 +87,7 @@
8787
"jsx-a11y/label-has-associated-control": "error",
8888
"jsx-a11y/no-autofocus": "off",
8989
"jsx-a11y/no-noninteractive-element-interactions": "error",
90-
"jsx-a11y/no-static-element-interactions": "off",
90+
"jsx-a11y/no-static-element-interactions": "error",
9191
"jsx-a11y/prefer-tag-over-role": "off",
9292
"jsx-a11y/anchor-ambiguous-text": "error",
9393
"jsx-a11y/anchor-has-content": "error",

apps/webapp/app/components/code/TSQLEditor.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ export function TSQLEditor(opts: TSQLEditorProps) {
264264

265265
const showButtons = showClearButton || showCopyButton || showFormatButton || additionalActions;
266266

267+
/* oxlint-disable jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions -- The CodeMirror mount forwards pointer focus to CodeMirror's own keyboard-accessible editor. */
267268
return (
268269
<div
269270
className={cn("relative flex h-full flex-col", opts.className)}
@@ -337,6 +338,7 @@ export function TSQLEditor(opts: TSQLEditorProps) {
337338
</div>
338339
);
339340
}
341+
/* oxlint-enable jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */
340342

341343
// SQL keywords that legitimately appear before parentheses with a space
342344
const SQL_KEYWORDS_BEFORE_PAREN = new Set([

apps/webapp/app/components/code/TSQLResultsTable.tsx

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
type ColumnFiltersState,
1515
type ColumnResizeMode,
1616
type FilterFn,
17+
type Header,
1718
type SortDirection,
1819
type SortingState,
1920
} from "@tanstack/react-table";
@@ -1044,6 +1045,24 @@ function FilterCell({
10441045
);
10451046
}
10461047

1048+
/* oxlint-disable jsx-a11y/no-static-element-interactions -- Column resizing is a pointer-drag interaction provided by TanStack Table. */
1049+
function ColumnResizeHandle({ header }: { header: Header<RowData, unknown> }) {
1050+
return (
1051+
<div
1052+
onDoubleClick={() => header.column.resetSize()}
1053+
onMouseDown={header.getResizeHandler()}
1054+
onTouchStart={header.getResizeHandler()}
1055+
className={cn(
1056+
"absolute right-0 top-0 h-full w-0.5 cursor-col-resize touch-none select-none",
1057+
"opacity-0 group-hover/header:opacity-100",
1058+
"bg-surface-control hover:bg-indigo-500",
1059+
header.column.getIsResizing() && "bg-indigo-500 opacity-100"
1060+
)}
1061+
/>
1062+
);
1063+
}
1064+
/* oxlint-enable jsx-a11y/no-static-element-interactions */
1065+
10471066
export const TSQLResultsTable = memo(function TSQLResultsTable({
10481067
rows,
10491068
columns,
@@ -1236,18 +1255,7 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({
12361255
>
12371256
{flexRender(header.column.columnDef.header, header.getContext())}
12381257
</HeaderCellContent>
1239-
{/* Column resizer */}
1240-
<div
1241-
onDoubleClick={() => header.column.resetSize()}
1242-
onMouseDown={header.getResizeHandler()}
1243-
onTouchStart={header.getResizeHandler()}
1244-
className={cn(
1245-
"absolute right-0 top-0 h-full w-0.5 cursor-col-resize touch-none select-none",
1246-
"opacity-0 group-hover/header:opacity-100",
1247-
"bg-surface-control hover:bg-indigo-500",
1248-
header.column.getIsResizing() && "bg-indigo-500 opacity-100"
1249-
)}
1250-
/>
1258+
<ColumnResizeHandle header={header} />
12511259
</th>
12521260
);
12531261
})}

apps/webapp/app/components/dashboard-agent/DashboardAgentPanel.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,7 @@ export function DashboardAgentPanel({
581581
// Not filtered to active: the wake banner needs watches that already fired.
582582
const chatWatches = activeChat?.watches ?? [];
583583

584+
/* oxlint-disable jsx-a11y/no-static-element-interactions -- Escape handling intentionally bubbles from focused controls inside the panel. */
584585
return (
585586
<div
586587
ref={panelRef}
@@ -667,3 +668,4 @@ export function DashboardAgentPanel({
667668
</div>
668669
);
669670
}
671+
/* oxlint-enable jsx-a11y/no-static-element-interactions */

apps/webapp/app/components/primitives/Input.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
9595
const inputClassName = variants[variant].input;
9696
const variantIconClassName = variants[variant].iconSize;
9797

98+
/* oxlint-disable jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions -- The wrapper only forwards pointer focus to its nested input. */
9899
return (
99100
<div
100101
className={cn(
@@ -125,6 +126,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
125126
);
126127
}
127128
);
129+
/* oxlint-enable jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */
128130
Input.displayName = "Input";
129131

130132
export { Input };

0 commit comments

Comments
 (0)