Skip to content

Clear aria-activedescendant while the highlighted row is unrendered; release 0.7.3 - #22

Merged
cmm-cmm merged 2 commits into
mainfrom
claude/readme-documentation-expansion-1j8wa1
Aug 2, 2026
Merged

Clear aria-activedescendant while the highlighted row is unrendered; release 0.7.3#22
cmm-cmm merged 2 commits into
mainfrom
claude/readme-documentation-expansion-1j8wa1

Conversation

@cmm-cmm

@cmm-cmm cmm-cmm commented Aug 2, 2026

Copy link
Copy Markdown
Owner

What & why

An accessibility defect found while auditing the virtual-scroll and keyboard paths.

updateActiveDescendant() set aria-activedescendant from the highlight index unconditionally. Virtualization drops off-window rows from the DOM, so scrolling a virtualized list past the highlighted option left the attribute pointing at an id with no element behind it. WAI-ARIA requires the reference to resolve to a real element; a dangling one reads to assistive technology as no active option at all, not as a stale one. Virtualization turns on automatically above ~100 rows, so this affected any large list — the library's headline feature.

Measured before the fix (jsdom, 1,000 options): highlight row 0, scroll the list, then

aria-activedescendant "forge-select-1-nav-0"
document.getElementById(...) null
first rendered row "Option 550"

The fix

The attribute is set only while the highlighted row is actually rendered, and keyboard navigation scrolls the row back into view and restores it. No API change — patch release (0.7.2 → 0.7.3).

Test plan

  • Updated relevant docs — n/a, no API surface change

  • Added a CHANGELOG.md entry (under the new [0.7.3] section being released here)

  • Added/updated tests — 160 passing (was 159). New test covers all three states (set → cleared on scroll → restored on ArrowDown) and was verified to fail without the fix: expected 'forge-select-1-nav-0' to be null

  • npm run verify passes locally — lint, format check, typecheck, coverage-enforced tests, build. Coverage up: branch 82.46 → 83.53%, lines 94.20 → 94.91%

  • Verified in real Chromium with actual wheel scrolling, not a scrollTop assignment:

    step aria-activedescendant element exists first rendered row
    after ArrowDown forge-select-1-nav-0 Option 0
    after mouse-wheel away null Option 95
    after ArrowDown again forge-select-1-nav-1 Option 0

    Zero console errors.

Audited without finding further defects

Recorded so the next pass can skip them: programmatic vs interactive clearing against the selection guards, tree cascade under maxSelections, selectAll vs the cap, setValue de-duplication and the missing-value policy, native multi-select and form-reset syncing, pagination page overlap, portal teardown, accent-strict search, className whitespace handling, i18n interpolation reaching the DOM, emitter re-entrancy, and variable-height (itemHeight: "auto") virtualization driven through a real browser — scrollHeight converges as rows are measured (21,696 → 22,224 over 600 uneven rows) and both ends of the list are reachable.


Generated by Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Fixed accessibility behavior in virtualized lists by clearing aria-activedescendant when the highlighted option is no longer rendered.
    • Restored the accessibility reference when keyboard navigation brings the highlighted option back into view.
  • Chores

    • Updated the package to version 0.7.3 and added release notes.

…release 0.7.3

Virtualization drops off-window rows from the DOM, but updateActiveDescendant()
set aria-activedescendant from the highlight index unconditionally. Scrolling a
virtualized list past the highlighted option therefore left the attribute
pointing at an id with no element behind it, which assistive technology reads as
no active option at all rather than as a stale one. Virtualization turns on
automatically above ~100 rows, so this affected any large list.

The attribute is now set only while the highlighted row is actually rendered,
and keyboard navigation scrolls the row back into view and restores it.

Verified in Chromium with real wheel scrolling: the reference exists after
ArrowDown, is cleared once the row scrolls out of the window, and is restored
pointing at a live element on the next ArrowDown.

Also audited without finding further defects: selection guards against
programmatic vs interactive clearing, tree cascade under maxSelections, native
multi-select and form-reset syncing, pagination page overlap, portal teardown,
accent-strict search, i18n interpolation, emitter re-entrancy, and
variable-height virtualization driven through a real browser.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 2, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
forge-select a4f582f Commit Preview URL

Branch Preview URL
Aug 02 2026, 11:30 AM

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@cmm-cmm, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 52 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9960e750-4f03-428e-9457-c7c4c25a3d2f

📥 Commits

Reviewing files that changed from the base of the PR and between 431938c and a4f582f.

📒 Files selected for processing (1)
  • src/ForgeSelect.ts
📝 Walkthrough

Walkthrough

The change fixes aria-activedescendant handling for highlighted rows removed by virtualization. It adds regression coverage for clearing and restoring the attribute, and updates the package and changelog versions to 0.7.3.

Changes

Virtualized accessibility release

Layer / File(s) Summary
Active descendant handling and regression coverage
src/ForgeSelect.ts, tests/forge-select.test.ts
aria-activedescendant is set only when the highlighted row exists in the DOM. Tests verify that the attribute is cleared when the row leaves the render window and restored when keyboard navigation renders it again.
Version and changelog update
package.json, CHANGELOG.md
The package version is 0.7.3. The changelog documents the fix and updates the release comparison links.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the accessibility fix and the 0.7.3 release.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/readme-documentation-expansion-1j8wa1

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…ight index

The first cut rebuilt the id from the highlight index and then probed the
document for it, which pushed the gzipped bundle to 13,504 against the 13,500
budget. main was already sitting at 13,495, so the gate had four bytes of room
and any fix of this shape would have tripped it.

Reading the id off the element the render pass just marked with
--highlighted removes both the id template and the lookup: the class and the id
are written together in renderRow, so the element is authoritative about whether
the highlighted row is currently rendered. 13,490 bytes — five below main while
fixing the bug.

Re-verified in Chromium across virtual (ArrowDown / scrolled away / restored /
End at row 999), a non-virtualized list, and the create row: the attribute
always matches the marked element's id or is absent, with no console errors.
@sonarqubecloud

sonarqubecloud Bot commented Aug 2, 2026

Copy link
Copy Markdown

@cmm-cmm
cmm-cmm merged commit 9aa08c6 into main Aug 2, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant