fix(web-shell): address review feedback on enhanced table controls (#8041)

This commit is contained in:
Qwen Code Autofix 2026-07-29 17:04:40 +00:00
parent 877c8e524c
commit fb9ac4a34c
3 changed files with 114 additions and 7 deletions

View file

@ -1,5 +1,6 @@
.tableShell {
position: relative;
--toolbar-height: 38px;
max-width: 100%;
margin: 8px 0;
overflow: visible;
@ -52,7 +53,7 @@
gap: 8px;
align-items: center;
box-sizing: border-box;
height: 38px;
height: var(--toolbar-height);
padding: 4px 10px;
border-bottom: 1px solid var(--border);
border-radius: 14px 14px 0 0;
@ -128,8 +129,6 @@
font-size: 12px;
}
.densityTrigger:focus,
.densityTrigger:focus-visible,
.densityTrigger[data-state='open'] {
border-color: transparent;
box-shadow: none;
@ -179,9 +178,14 @@
outline: none;
}
.scroller:focus-visible {
outline: 2px solid var(--agent-blue-500);
outline-offset: -2px;
}
.frozenColumnShadow {
position: absolute;
top: 38px;
top: var(--toolbar-height);
bottom: 0;
z-index: 9;
width: 14px;

View file

@ -1448,6 +1448,30 @@ describe('EnhancedMarkdownTable', () => {
);
});
it('spans the detail row across the filler column', () => {
const container = renderTable();
for (const column of ['Team', 'Score']) {
act(() => {
button(container, `Resize ${column}`).dispatchEvent(
new KeyboardEvent('keydown', {
bubbles: true,
key: 'ArrowRight',
}),
);
});
}
expect(container.querySelector('[class*="fillerColumn"]')).not.toBeNull();
click(button(container, 'View details for row 1'));
const detailCell = container.querySelector<HTMLTableCellElement>(
'[class*="detailCell"]',
);
expect(detailCell).not.toBeNull();
expect(detailCell!.colSpan).toBe(4);
});
it('resizes compact auto columns from their rendered width with keyboard arrows', () => {
const container = renderTable();
selectValue(button(container, 'Table density'), 'compact');
@ -1645,6 +1669,68 @@ describe('EnhancedMarkdownTable', () => {
).toBe('280px');
});
it('repositions the frozen shadow when the ResizeObserver fires', () => {
const callbacks: ResizeObserverCallback[] = [];
const OriginalResizeObserver = globalThis.ResizeObserver;
class CapturingResizeObserver {
constructor(private readonly callback: ResizeObserverCallback) {
callbacks.push(callback);
}
observe() {}
unobserve() {}
disconnect() {}
}
(globalThis as { ResizeObserver?: unknown }).ResizeObserver =
CapturingResizeObserver;
try {
const container = renderWideTable();
const shell = container.querySelector<HTMLElement>(
'[class*="tableShell"]',
);
const header = button(container, 'Sort by Team').closest('th');
expect(shell).not.toBeNull();
expect(header).not.toBeNull();
Object.defineProperty(shell, 'clientLeft', {
configurable: true,
value: 1,
});
Object.defineProperty(shell, 'getBoundingClientRect', {
configurable: true,
value: () => ({ left: 20 }) as DOMRect,
});
Object.defineProperty(header, 'getBoundingClientRect', {
configurable: true,
value: () => ({ right: 301 }) as DOMRect,
});
freezeFirstColumn(container);
expect(
container.querySelector<HTMLElement>('[class*="frozenColumnShadow"]')
?.style.left,
).toBe('280px');
Object.defineProperty(header, 'getBoundingClientRect', {
configurable: true,
value: () => ({ right: 351 }) as DOMRect,
});
act(() => {
for (const cb of callbacks) {
cb([], {} as ResizeObserver);
}
});
expect(
container.querySelector<HTMLElement>('[class*="frozenColumnShadow"]')
?.style.left,
).toBe('330px');
} finally {
(globalThis as { ResizeObserver?: unknown }).ResizeObserver =
OriginalResizeObserver;
}
});
it('dismisses the first-column context menu without clearing the active column', () => {
const container = renderWideTable();
@ -2057,6 +2143,24 @@ describe('EnhancedMarkdownTable', () => {
expect(detailsButton.getAttribute('aria-expanded')).toBe('true');
});
it('falls back to window.scrollBy when no scroll ancestor exists', () => {
const container = renderTable();
const scrollBySpy = vi
.spyOn(window, 'scrollBy')
.mockImplementation(() => {});
const detailsButton = button(container, 'View details for row 3');
const row = detailsButton.closest('tr');
expect(row).not.toBeNull();
vi.spyOn(row!, 'getBoundingClientRect')
.mockReturnValueOnce({ top: 120 } as DOMRect)
.mockReturnValueOnce({ top: 280 } as DOMRect);
click(detailsButton);
expect(scrollBySpy).toHaveBeenCalledWith(0, 160);
expect(detailsButton.getAttribute('aria-expanded')).toBe('true');
});
it('shows statistics for a numeric selection', () => {
const container = renderTable();

View file

@ -182,6 +182,7 @@ const NUMBER_FILTER_LABEL_KEYS: Record<NumberFilterOperator, string> = {
export const MAX_ENHANCED_TABLE_ROWS = 500;
export const MAX_ENHANCED_TABLE_COLUMNS = 50;
// Must stay in sync with --action-column-width in EnhancedMarkdownTable.module.css
const ACTION_COLUMN_WIDTH = 40;
const DEFAULT_COLUMN_WIDTH = 160;
const COMPACT_COLUMN_WIDTH = 72;
@ -2175,9 +2176,8 @@ export function EnhancedTable({
minWidth: number,
fixedWidth: number,
flexibleColumnCount: number,
containerWidth: '100cqw' | '100%',
): string =>
`max(${minWidth}px, calc((${containerWidth} - ${ACTION_COLUMN_WIDTH}px - ${fixedWidth}px) / ${flexibleColumnCount}))`;
`max(${minWidth}px, calc((100cqw - ${ACTION_COLUMN_WIDTH}px - ${fixedWidth}px) / ${flexibleColumnCount}))`;
const columnGroupStyle = (columnIndex: number): CSSProperties => {
const width = columnWidths[columnIndex];
@ -2190,7 +2190,6 @@ export function EnhancedTable({
minWidth,
fixedVisibleColumnWidth,
flexibleColumnCount,
'100cqw',
),
};
};