Site builder: security & data-loss hardening + unified asset picker + audit backlog #3

Merged
jknapp merged 61 commits from builder-hardening-2026-07 into main 2026-07-13 01:13:28 +00:00
2 changed files with 37 additions and 0 deletions
Showing only changes of commit 99cc4c79f2 - Show all commits
+31
View File
@@ -48,4 +48,35 @@ describe('clickableProps', () => {
expect(onActivate).not.toHaveBeenCalled(); expect(onActivate).not.toHaveBeenCalled();
expect(e.preventDefault).not.toHaveBeenCalled(); expect(e.preventDefault).not.toHaveBeenCalled();
}); });
test('onKeyDown activates when the event target is the row itself', () => {
const onActivate = vi.fn();
const props = clickableProps(onActivate);
const rowEl = {};
const e = {
key: 'Enter',
target: rowEl,
currentTarget: rowEl,
preventDefault: vi.fn(),
} as unknown as Parameters<ReturnType<typeof clickableProps>['onKeyDown']>[0];
props.onKeyDown(e);
expect(onActivate).toHaveBeenCalledTimes(1);
expect(e.preventDefault).toHaveBeenCalledTimes(1);
});
test('onKeyDown ignores a bubbled event from a nested control (e.g. a child button)', () => {
const onActivate = vi.fn();
const props = clickableProps(onActivate);
const rowEl = {};
const childButtonEl = {};
const e = {
key: 'Enter',
target: childButtonEl,
currentTarget: rowEl,
preventDefault: vi.fn(),
} as unknown as Parameters<ReturnType<typeof clickableProps>['onKeyDown']>[0];
props.onKeyDown(e);
expect(onActivate).not.toHaveBeenCalled();
expect(e.preventDefault).not.toHaveBeenCalled();
});
}); });
+6
View File
@@ -21,6 +21,12 @@ export function clickableProps(onActivate: () => void): ClickableProps {
tabIndex: 0, tabIndex: 0,
onClick: onActivate, onClick: onActivate,
onKeyDown: (e: KeyboardEvent) => { onKeyDown: (e: KeyboardEvent) => {
// Ignore keydown events that bubbled up from a nested interactive
// element (e.g. a child <button>). Handling only events targeted at
// this element lets the browser's native "click" synthesis for a
// focused child control proceed instead of being hijacked by the
// row's own Enter/Space activation.
if (e.target !== e.currentTarget) return;
if (e.key === 'Enter' || e.key === ' ') { if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault(); e.preventDefault();
onActivate(); onActivate();