Skip to content

Commit 0187d2a

Browse files
committed
fix(webapp): enforce keyboard interaction safeguards
1 parent a3d702a commit 0187d2a

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";
@@ -1031,6 +1032,24 @@ function FilterCell({
10311032
);
10321033
}
10331034

1035+
/* oxlint-disable jsx-a11y/no-static-element-interactions -- Column resizing is a pointer-drag interaction provided by TanStack Table. */
1036+
function ColumnResizeHandle({ header }: { header: Header<RowData, unknown> }) {
1037+
return (
1038+
<div
1039+
onDoubleClick={() => header.column.resetSize()}
1040+
onMouseDown={header.getResizeHandler()}
1041+
onTouchStart={header.getResizeHandler()}
1042+
className={cn(
1043+
"absolute right-0 top-0 h-full w-0.5 cursor-col-resize touch-none select-none",
1044+
"opacity-0 group-hover/header:opacity-100",
1045+
"bg-surface-control hover:bg-indigo-500",
1046+
header.column.getIsResizing() && "bg-indigo-500 opacity-100"
1047+
)}
1048+
/>
1049+
);
1050+
}
1051+
/* oxlint-enable jsx-a11y/no-static-element-interactions */
1052+
10341053
export const TSQLResultsTable = memo(function TSQLResultsTable({
10351054
rows,
10361055
columns,
@@ -1223,18 +1242,7 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({
12231242
>
12241243
{flexRender(header.column.columnDef.header, header.getContext())}
12251244
</HeaderCellContent>
1226-
{/* Column resizer */}
1227-
<div
1228-
onDoubleClick={() => header.column.resetSize()}
1229-
onMouseDown={header.getResizeHandler()}
1230-
onTouchStart={header.getResizeHandler()}
1231-
className={cn(
1232-
"absolute right-0 top-0 h-full w-0.5 cursor-col-resize touch-none select-none",
1233-
"opacity-0 group-hover/header:opacity-100",
1234-
"bg-surface-control hover:bg-indigo-500",
1235-
header.column.getIsResizing() && "bg-indigo-500 opacity-100"
1236-
)}
1237-
/>
1245+
<ColumnResizeHandle header={header} />
12381246
</th>
12391247
);
12401248
})}

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)