Skip to content

fix(modal): treat overflow: hidden as cropping, skip inline and display: contents ancestors - #3557

Merged
chuckcarpenter merged 7 commits into
mainfrom
claude/hopeful-carson-0vsqdj-3484
Oct 7, 2026
Merged

chuckcarpenter merged 7 commits into
mainfrom
claude/hopeful-carson-0vsqdj-3484

Conversation

@chuckcarpenter

@chuckcarpenter chuckcarpenter commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

Fixes #3484.

Builds on #3556 (merged), which covered the root-element half of this issue: <html> never clips, and <body> clips only when its overflow doesn't propagate to the viewport. This PR now targets main directly.

Change

_isScrollable in shepherd.js/src/components/shepherd-modal.ts:

  • Accepts hidden alongside auto, scroll and clip. hidden crops exactly like the others; it just can't be scrolled by the user. Excluding it was also inconsistent with already accepting clip.
  • Skips display: inline and display: contents ancestors. Overflow doesn't apply to non-replaced inlines, whose rect is the union of their line boxes. contents generates no box, so its rect is 0×0 at the origin and would zero every opening beneath it.
  • Drops scrollHeight >= clientHeight. Per CSSOM-View the scrolling area is at least the padding box, so the term was true for every element and filtered nothing.
  • The propagated body { height: 100vh; overflow: hidden } case from the issue is already handled by fix(modal): don't clip highlights by root elements whose overflow applies to the viewport #3556's root-awareness.

Verified in Chromium

case main (with #3556) this PR
highlight inside collapsed height:0; overflow:hidden accordion 80px hole over hidden content height 0 ✅
body{height:100vh;overflow:hidden}, target scrolled into view 40 40 ✅ (from the issue: { y: 202, height: 40 } stays correct)
target in overflow:hidden inline span 100 100 ✅ (17 if the display check is removed)
target under display: contents; overflow: hidden 60 60 ✅ (0 if the display check is removed)
real body scroller (html{overflow:hidden}), target below its fold 0 0 ✅

Tests

  • Unit: a new "which overflow ancestors crop" block covers hidden, clip and auto cropping; visible not cropping; and inline / contents never cropping. The hidden case fails on main. mockOverflow gained an optional displays map.
  • Cypress: this adds overflow-clipping.cy.js with examples/overflow-clipping.html (accordion, inline, contents) and adds the propagated overflow: hidden body case to root-overflow.cy.js. As the issue notes, these layouts can't be expressed in happy-dom. The same example pages were also driven in Chromium through Playwright; the results are the table above.
  • vitest run: 300/300 pass; eslint, prettier (js/ts), and tsc are clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_011RqamMhESzSaWmRHLw2Vai

Summary by CodeRabbit

  • Bug Fixes
    • Corrected modal highlight clipping around overflow containers. Hidden, clipped, and scrollable containers now crop highlights, while inline and contents elements do not.
    • Improved highlight alignment when page-level overflow is hidden.

claude added 2 commits October 6, 2026 17:04
…lies to the viewport

`_isScrollable` treated <body> as a scroll container whenever its computed
overflow-y was `auto`/`scroll`. But while <html> is `overflow: visible`,
body's overflow propagates to the viewport and body's used overflow is
`visible`, so it crops nothing. With body sized to the viewport
(`height: 100%`), scrolling a target below the initial fold into view moved
body's rect off-screen and the overlay opening collapsed to zero height,
covering the target it was meant to highlight.

<html> is now never treated as a clipper (its overflow always applies to
the viewport), and <body> only when <html>'s overflow is not `visible`.

Fixes #1984

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011RqamMhESzSaWmRHLw2Vai
…ay: contents ancestors

`_isScrollable` excluded `overflow-y: hidden`, so a highlight inside a
collapsed accordion, carousel track or programmatically scrolled hidden pane
got a full-size opening over content the user cannot see. It already
accepted `clip`, which crops identically, so the handling was inconsistent.

`hidden` is now accepted alongside `auto`, `scroll` and `clip`. To keep that
from regressing boxes whose computed overflow does not crop:
- the root elements are already handled (#1984), which covers a propagated
  `body { overflow: hidden }`;
- non-replaced inlines (overflow does not apply) and `display: contents`
  (no box, 0x0 rect at the origin) are skipped.

The `scrollHeight >= clientHeight` term is dropped: per CSSOM-View the
scrolling area is at least the padding box, so it was true for every element
and filtered nothing.

Fixes #3484

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011RqamMhESzSaWmRHLw2Vai
@vercel

vercel Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
shepherd-docs Ready Ready Preview Oct 7, 2026 6:37am UTC
1 Skipped Deployment
Project Deployment Actions Updated
shepherd-landing Skipped Skipped Oct 7, 2026 6:37am UTC

Request Review

@coderabbitai

coderabbitai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c984b30f-098d-4440-a88f-30fc1bb2ac82
📥 Commits

Reviewing files that changed from the base of the PR and between cd3042b and a98903c.

📒 Files selected for processing (5)
  • shepherd.js/src/components/shepherd-modal.ts
  • shepherd.js/test/cypress/examples/overflow-clipping.html
  • shepherd.js/test/cypress/integration/overflow-clipping.cy.js
  • shepherd.js/test/cypress/integration/root-overflow.cy.js
  • shepherd.js/test/unit/components/shepherd-modal.spec.js

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The modal clipping predicate now treats non-visible vertical overflow as clipping, except for elements with inline or contents display. Unit and Cypress tests cover overflow clipping and overlay openings.

Changes

Modal overlay clipping

Layer / File(s) Summary
Clipping predicate and unit coverage
shepherd.js/src/components/shepherd-modal.ts, shepherd.js/test/unit/components/shepherd-modal.spec.js
_isScrollable skips inline and contents elements and no longer checks scroll and client heights. Unit tests cover hidden, clip, auto, visible, inline, and contents cases.
Browser coverage for overlay openings
shepherd.js/test/cypress/examples/overflow-clipping.html, shepherd.js/test/cypress/integration/overflow-clipping.cy.js, shepherd.js/test/cypress/integration/root-overflow.cy.js
The Cypress example and tests cover a collapsed overflow ancestor, inline and contents elements, and body overflow.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to a9890

No identified issue prevents merging after normal checks.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 4 files. (1 skipped: 1… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed #3484 is the only active directly linked issue. _isScrollable excludes documentElement, excludes body when overflow propagates to the viewport, and accounts for containment and root overflow. It…
Out of Scope Changes check ✅ Passed The reported source changes and tests all support #3484’s overflow-clipping behavior and regression coverage. No unrelated changes are identified.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: it treats hidden overflow as cropping and skips inline and contents ancestors.
Full details: Docstring Coverage

Explanation

Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 4 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@qltysh

qltysh Bot commented Oct 6, 2026

Copy link
Copy Markdown

Qlty


Coverage Impact

This PR will not change total coverage.

Modified Files with Diff Coverage (1)

RatingFile% DiffUncovered Line #s
Coverage rating: A Coverage rating: A
shepherd.js/src/components/shepherd-modal.ts100.0%
Total100.0%
🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

claude added 2 commits October 6, 2026 17:15
…opagation

Any `contain` value on <html> or <body> stops body's overflow propagating
to the viewport, leaving body a real scroll container even while <html> is
`overflow: visible` (verified in Chromium for layout, paint, size, style,
content, strict and inline-size). Only skip body when neither is contained.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011RqamMhESzSaWmRHLw2Vai
…n-0vsqdj-3484

# Conflicts:
#	shepherd.js/test/unit/components/shepherd-modal.spec.js
claude added 2 commits October 6, 2026 17:26
Body's overflow propagates to the viewport only while the root's overflow
is `visible` on both axes. With `html { overflow-x: clip }`, overflow-y
still computes to `visible` but body keeps its own scroll container
(verified in Chromium), so check overflowX too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011RqamMhESzSaWmRHLw2Vai
…n-0vsqdj-3484

# Conflicts:
#	shepherd.js/test/unit/components/shepherd-modal.spec.js
…-0vsqdj-3484

# Conflicts:
#	shepherd.js/src/components/shepherd-modal.ts
#	shepherd.js/test/cypress/integration/root-overflow.cy.js
#	shepherd.js/test/unit/components/shepherd-modal.spec.js
@vercel
vercel Bot temporarily deployed to Preview – shepherd-landing October 7, 2026 06:37 Inactive
@chuckcarpenter
chuckcarpenter merged commit 68e0bc6 into main Oct 7, 2026
8 checks passed
@chuckcarpenter
chuckcarpenter deleted the claude/hopeful-carson-0vsqdj-3484 branch October 7, 2026 06:51
@github-actions github-actions Bot mentioned this pull request Oct 7, 2026

This branch was successfully deployed

1 active and 1 inactive deployments
Preview – shepherd-docs — a98903cf Deployed Oct 7, 2026 by vercel[bot]
Preview – shepherd-landing — a98903cf Deployed Oct 7, 2026 by vercel[bot]
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.

Overlay clipping: _isScrollable should be root- and display-aware, and treat overflow: hidden as cropping

2 participants