Skip to content

fix: make canClickTarget work when a classPrefix is set - #3481

Open
chuckcarpenter wants to merge 1 commit into
mainfrom
fix/1298-class-prefix-target-blocking
Open

fix: make canClickTarget work when a classPrefix is set#3481
chuckcarpenter wants to merge 1 commit into
mainfrom
fix/1298-class-prefix-target-blocking

Conversation

@chuckcarpenter

@chuckcarpenter chuckcarpenter commented Aug 13, 2026

Copy link
Copy Markdown
Member

Step adds shepherd-enabled and shepherd-target to the target with the
tour's classPrefix, but adds shepherd-target-click-disabled unprefixed.
The rule that implements click blocking required all three unprefixed on the
same element, so with classPrefix: 'my-tour-' the target ended up with
shepherd-target-click-disabled my-tour-shepherd-enabled my-tour-shepherd-target,
the selector could not match, and canClickTarget: false silently did nothing.

Key the rule solely on shepherd-target-click-disabled. That class is already
unprefixed and is added only when canClickTarget === false, so the other two
carried no semantic weight — they were only ever incidental. A static
stylesheet cannot know the runtime prefix, so dropping them is the fix. The
class is repeated three times to hold the selector at its original 0-3-0
specificity, leaving cascade weight unchanged for anyone already overriding it;
cssnano preserves the repetition, so the shipped dist/css/shepherd.css keeps
0-3-0 as well.

The cypress tests attach to a new fixture in the dummy page whose target and
child each carry a competing pointer-events: auto declaration — 0-2-0 on the
target, 0-1-0 on the child. Without that competition neither half of the rule
is actually under test: pointer-events is inherited, so a plain child of a
blocked target computes none even with the ... * clause deleted, and
nothing else on the page contests the specificity. With the fixture, collapsing
the repeated class or deleting the descendant clause each turns the tests red.

Three caveats worth knowing, since the bug has been latent since 2021:

  • An app that set canClickTarget: false under a classPrefix and came to
    rely on the target staying clickable will now find it blocked. The escape
    hatch is canClickTarget: true, or omitting the option.
  • Any element that application code manually tagged with
    shepherd-target-click-disabled outside a tour now gets
    pointer-events: none. That class is Shepherd-internal and named for
    exactly this effect.
  • A prefixed tour can now trap itself, exactly as an unprefixed one already
    could. If stepsContainer puts the popup inside the target, or an ancestor
    of the popup (document.body, say) is listed in extraHighlights, the
    descendant half of the rule kills the tour's own buttons. Those steps need
    canClickTarget: true.

One pre-existing leak is disclosed rather than silently carried. Calling
Step#show() directly — rather than through Tour#show, which hides the
current step first — re-resolves extraHighlights before tearing the step
down, so elements that have since dropped out of the selector keep the classes
they were given. Under a prefix that leftover shepherd-target-click-disabled
used to be inert and is now permanent pointer-events: none. The same call
leaks highlightClass and the prefixed shepherd-enabled/shepherd-target
identically, so it is a _show ordering bug that predates this change and
wants its own commit, not a widening of a CSS selector fix. The attachTo
target itself is not affected: _setupElements tears down while step.target
still points at the old element, and only setupTooltip reassigns it.

landing/src/styles/shepherd.css is a hand-vendored copy of the minified
stylesheet that nothing in landing's build regenerates. Its selector is updated
here too, so shepherdjs.dev stops shipping a stylesheet that still has the bug.

Tours without a classPrefix are entirely unaffected: the new selector matches
a strict superset of the old one at identical specificity.

Fixes #1298

Verification

  • 233 unit tests, Cypress green, lint / prettier / types:check clean.
  • The Cypress fixture deliberately competes with the rule (pointer-events: auto at 0-2-0 on the target, 0-1-0 on the child). Without that competition neither half of the rule is actually under test — pointer-events is inherited, so a plain child computes none even with the ... * clause deleted. With it, collapsing the repeated class or deleting the descendant clause each turns the tests red.

Open since Jan 2021 and picked up by two contributors who both bounced off it. The sticking point is that a static stylesheet cannot know the runtime prefix, which is why this drops the prefixed classes from the selector rather than trying to prefix the CSS.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved target click blocking when canClickTarget is disabled, including child elements.
    • Ensured click blocking continues to work when a custom classPrefix is configured.
    • Improved compatibility with custom styles that define pointer-event behavior.
  • Documentation

    • Clarified which classes and data attributes are affected by classPrefix.
    • Documented stylesheet requirements for disabling target interaction with canClickTarget: false.

`Step` adds `shepherd-enabled` and `shepherd-target` to the target with the
tour's `classPrefix`, but adds `shepherd-target-click-disabled` unprefixed.
The rule that implements click blocking required all three unprefixed on the
same element, so with `classPrefix: 'my-tour-'` the target ended up with
`shepherd-target-click-disabled my-tour-shepherd-enabled my-tour-shepherd-target`,
the selector could not match, and `canClickTarget: false` silently did nothing.

Key the rule solely on `shepherd-target-click-disabled`. That class is already
unprefixed and is added only when `canClickTarget === false`, so the other two
carried no semantic weight — they were only ever incidental. A static
stylesheet cannot know the runtime prefix, so dropping them is the fix. The
class is repeated three times to hold the selector at its original 0-3-0
specificity, leaving cascade weight unchanged for anyone already overriding it;
cssnano preserves the repetition, so the shipped dist/css/shepherd.css keeps
0-3-0 as well.

The cypress tests attach to a new fixture in the dummy page whose target and
child each carry a competing `pointer-events: auto` declaration — 0-2-0 on the
target, 0-1-0 on the child. Without that competition neither half of the rule
is actually under test: `pointer-events` is inherited, so a plain child of a
blocked target computes `none` even with the `... *` clause deleted, and
nothing else on the page contests the specificity. With the fixture, collapsing
the repeated class or deleting the descendant clause each turns the tests red.

Three caveats worth knowing, since the bug has been latent since 2021:

- An app that set `canClickTarget: false` under a `classPrefix` and came to
  rely on the target staying clickable will now find it blocked. The escape
  hatch is `canClickTarget: true`, or omitting the option.
- Any element that application code manually tagged with
  `shepherd-target-click-disabled` outside a tour now gets
  `pointer-events: none`. That class is Shepherd-internal and named for
  exactly this effect.
- A prefixed tour can now trap itself, exactly as an unprefixed one already
  could. If `stepsContainer` puts the popup inside the target, or an ancestor
  of the popup (`document.body`, say) is listed in `extraHighlights`, the
  descendant half of the rule kills the tour's own buttons. Those steps need
  `canClickTarget: true`.

One pre-existing leak is disclosed rather than silently carried. Calling
`Step#show()` directly — rather than through `Tour#show`, which hides the
current step first — re-resolves `extraHighlights` before tearing the step
down, so elements that have since dropped out of the selector keep the classes
they were given. Under a prefix that leftover `shepherd-target-click-disabled`
used to be inert and is now permanent `pointer-events: none`. The same call
leaks `highlightClass` and the prefixed `shepherd-enabled`/`shepherd-target`
identically, so it is a `_show` ordering bug that predates this change and
wants its own commit, not a widening of a CSS selector fix. The `attachTo`
target itself is not affected: `_setupElements` tears down while `step.target`
still points at the old element, and only `setupTooltip` reassigns it.

landing/src/styles/shepherd.css is a hand-vendored copy of the minified
stylesheet that nothing in landing's build regenerates. Its selector is updated
here too, so shepherdjs.dev stops shipping a stylesheet that still has the bug.

Tours without a `classPrefix` are entirely unaffected: the new selector matches
a strict superset of the old one at identical specificity.

Fixes #1298

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 13, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
shepherd-docs Ready Ready Preview Aug 13, 2026 1:58pm
shepherd-landing Ready Ready Preview Aug 13, 2026 1:58pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 251e8f9d-cc91-4f74-8548-3b68ecd2067f

📥 Commits

Reviewing files that changed from the base of the PR and between 1d9c664 and 3757608.

📒 Files selected for processing (10)
  • docs-src/src/content/docs/guides/styling.md
  • docs-src/src/content/docs/guides/usage.md
  • landing/src/styles/shepherd.css
  • shepherd.js/src/components/shepherd-element.css
  • shepherd.js/src/step.ts
  • shepherd.js/src/tour.ts
  • shepherd.js/test/cypress/dummy/css/welcome.css
  • shepherd.js/test/cypress/dummy/index.html
  • shepherd.js/test/cypress/integration/element-targeting.cy.js
  • shepherd.js/test/unit/step.spec.js

📝 Walkthrough

Walkthrough

The change keeps shepherd-target-click-disabled unprefixed and updates Shepherd CSS to block pointer events independently of classPrefix. Documentation, Cypress fixtures, and unit tests cover target, descendant, extra-highlight, and cleanup behavior.

Changes

Target click blocking

Layer / File(s) Summary
Click-blocking contract
docs-src/src/content/docs/guides/styling.md, docs-src/src/content/docs/guides/usage.md, shepherd.js/src/step.ts, shepherd.js/src/tour.ts
Documentation defines which identifiers classPrefix changes and states that click blocking requires Shepherd CSS or an equivalent rule.
Click-blocking CSS
landing/src/styles/shepherd.css, shepherd.js/src/components/shepherd-element.css
Selectors apply pointer-events: none to .shepherd-target-click-disabled elements and descendants without requiring prefixed runtime classes.
Click-blocking validation
shepherd.js/test/cypress/dummy/*, shepherd.js/test/cypress/integration/element-targeting.cy.js, shepherd.js/test/unit/step.spec.js
Fixtures and tests cover default behavior, disabled targets, custom classPrefix, extra highlights, and cleanup.

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

Mergeability Score: ⚪ Minimal · up to 37576

This localized fix makes click blocking work for tours that use a classPrefix while preserving existing specificity and behavior for unprefixed tours. No actionable merge-blocking risk remains after normal checks and review.

Suggested reviewers: robbiethewagner

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 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 fix for canClickTarget when classPrefix is configured.
Linked Issues check ✅ Passed The CSS change and tests directly resolve issue #1298 by preserving target blocking with classPrefix.
Out of Scope Changes check ✅ Passed The stylesheet, documentation, and tests are directly related to the target-blocking fix.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/1298-class-prefix-target-blocking

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

shepherd.js/test/cypress/integration/element-targeting.cy.js

(node:2) ESLintIgnoreWarning: The ".eslintignore" file is no longer supported. Switch to using the "ignores" property in "eslint.config.js": https://eslint.org/docs/latest/use/configure/migration-guide#ignore-files
(Use node --trace-warnings ... to show where the warning was created)

Oops! Something went wrong! :(

ESLint: 10.8.1

A config object is using the "root" key, which is not supported in flat config system.

Flat configs always act as if they are the root config file, so this key can be safely removed.

shepherd.js/test/unit/step.spec.js

(node:2) ESLintIgnoreWarning: The ".eslintignore" file is no longer supported. Switch to using the "ignores" property in "eslint.config.js": https://eslint.org/docs/latest/use/configure/migration-guide#ignore-files
(Use node --trace-warnings ... to show where the warning was created)

Oops! Something went wrong! :(

ESLint: 10.8.1

A config object is using the "root" key, which is not supported in flat config system.

Flat configs always act as if they are the root config file, so this key can be safely removed.


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 Aug 13, 2026

Copy link
Copy Markdown

Qlty


Coverage Impact

⬆️ Merging this pull request will increase total coverage on main by 1.2%.

🚦 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.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

classPrefix breaks target blocking

1 participant