Skip to content

[WC-3539] fix(gallery-web): fix issue with nested inputs - #2428

Open
iobuhov wants to merge 1 commit into
mainfrom
3539/gallery-keyboard-interaction-fix
Open

iobuhov wants to merge 1 commit into
mainfrom
3539/gallery-keyboard-interaction-fix

Conversation

@iobuhov

@iobuhov iobuhov commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

Pull request type

Bug fix (non-breaking change which fixes an issue)


Description

Fix bug where gallery on click action prevents default action for SPACE and ENTER

@github-actions

Copy link
Copy Markdown
Contributor

AI Code Review

🔶 Changes requested — one or more medium-severity items must be addressed


What was reviewed

File Change
packages/pluggableWidgets/gallery-web/src/components/ListItemButton.tsx Add isOwn guard to onKeyDown so preventAndStop and pressed are only set for own events
packages/pluggableWidgets/gallery-web/src/features/item-interaction/action-handlers.ts Add isOwn guard to canExecOnSpaceOrEnter filter
packages/shared/widget-plugin-grid/src/selection/keyboard.ts Add isOwn guard to isSelectOneTrigger and all arrow/scroll-key filters in onSelectGridAdjacentHotKey
packages/pluggableWidgets/gallery-web/CHANGELOG.md New [Unreleased] Fixed entry
packages/pluggableWidgets/gallery-web/openspec/** Archived change spec artifacts

Skipped (out of scope): dist/, pnpm-lock.yaml


Findings

🔶 Medium — Module-level singleton kbdHandlers shared across all ListItemButton instances

File: packages/pluggableWidgets/gallery-web/src/components/ListItemButton.tsx lines 39, 21–22

Problem: kbdHandlers is created once at module load time by calling keyboardHandlers(), which closes over a single pressed flag. Every ListItemButton on the page shares that flag. If the user presses Space/Enter on one gallery item and releases on another (or after focus moves), pressed will be stale and the action will either fire incorrectly or silently skip. This was pre-existing, but the PR is fixing keyboard-handling correctness — the singleton makes the new ownership semantics unreliable across instances.

Fix: Create handlers per-instance:

export function ListItemButton(props: Omit<JSX.IntrinsicElements["div"], "ref">): ReactElement {
    const [handlers] = useState(keyboardHandlers); // stable, called once per mount
    return (
        <div
            {...props}
            {...handlers}
            className={classNames("widget-gallery-item-button", props.className)}
            role="button"
        />
    );
}

🔶 Medium — No unit tests for the new ownership check (nested-element case)

File: packages/pluggableWidgets/gallery-web/src/features/item-interaction/__tests__/item-keyboard.spec.tsx

Problem: Every existing test fires events directly on div[role="listitem"], so event.target === event.currentTarget is always true — the new isOwn guard is never exercised in the failing path. A regression (dropping the guard) would not be caught.

Fix: Add a bubbled-event case:

test("does NOT execute action when Space is pressed on a nested input", async () => {
    const onExecuteAction = jest.fn();
    const [item] = objectItems(1);
    const props = eventSwitch<EventEntryContext, HTMLDivElement>(
        () => ({ item, selectionType: "None", selectionMode: "clear", clickTrigger: "single" }),
        [...createActionHandlers(onExecuteAction)]
    );
    const { user } = setup(
        <div role="listitem" tabIndex={0} {...props}>
            <input data-testid="nested-input" />
        </div>
    );
    await user.click(screen.getByTestId("nested-input"));
    await user.keyboard("[Space]");
    expect(onExecuteAction).toHaveBeenCalledTimes(0);
});

A parallel test should cover ListItemButton — assert event.preventDefault is NOT called when the event bubbles from a child element.


⚠️ Low — isSelectOneTrigger change silently affects datagrid-web

File: packages/shared/widget-plugin-grid/src/selection/keyboard.ts line 14

Note: isSelectOneTrigger is used by both gallery-web and datagrid-web. Adding the isOwn check means that in a datagrid, pressing Shift+Space while focus is on a nested child within a row will no longer toggle row selection. This is likely correct behaviour, but it is a behavioural change in datagrid-web not mentioned in the PR description. Confirm this is intentional.


Positives

  • The isOwn check is placed in the filter of canExecOnSpaceOrEnter rather than inside the handler, so the pressed state in onSpaceOrEnter is never dirtied by bubbled events — correct design.
  • preventAndStop in ListItemButton.onKeyDown is correctly gated by isOwn before setting pressed, preventing onKeyUp from firing for an event whose keydown was on a nested element.
  • CHANGELOG entry is user-facing, describes before/after behaviour, and uses correct Keep a Changelog format.
  • Archived spec artifacts include design rationale documenting the currentTarget === target pattern and the capture-phase alternative considered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant