Skip to content

fix(interact): honor selector conditions on viewProgress and pointerMove - #344

Merged
eliranhe merged 1 commit into
masterfrom
fix/scrub-triggers-selector-condition
Oct 22, 2026
Merged

eliranhe merged 1 commit into
masterfrom
fix/scrub-triggers-selector-condition

Conversation

@eliranhe

Copy link
Copy Markdown
Contributor

Description

Bug fix: viewProgress and pointerMove ignore an effect's selector condition.

core/add.ts resolves the effect's selector condition and passes it to every trigger handler as selectorCondition in InteractOptions. click, hover, viewEnter and animationEnd skip a target that doesn't match it (if (selectorCondition && !target.matches(selectorCondition)) return;), and the CSS that generate() writes is scoped by it too. viewProgress and pointerMove never read it. Every target got the scroll-driven animation or the pointer effect, so an effect meant for part of a list (e.g. every other repeater item) ran on all of them.

Fix: both handlers now read selectorCondition and return early when the target doesn't match. Scrub effects have no per-event callback to check in (they're driven continuously by the ViewTimeline / fizban Scroll / kuliso Pointer), so the check runs once, when the handler is added:

  • viewProgress: no getWebAnimation animation (native ViewTimeline) or getScrubScene + Scroll (fizban polyfill) is created or played for a non-matching target.
  • pointerMove: no getScrubScene + Pointer is created for a non-matching target.

Related Issue

Found while generating interact configs for Wix sites in the Thunderbolt viewer, which expresses repeater "pattern" effects as an effect-level selector condition, e.g.

conditions: { pattern: { type: 'selector', predicate: '[role="list"] > :nth-child(2n) &' } }

The CSS from generate() honors it, but the scroll and pointer effects ran on every repeater item. Thunderbolt currently works around this by folding the pattern into the effect's selector. That workaround can be removed once a release with this fix is bumped downstream.

Checklist

  • I have read the Contributing Guide
  • I have added/updated tests for my changes (if applicable)
  • I have updated documentation/rules/skills (if applicable): not needed. rules/full-lean.md already says a selector condition on an effect skips only that effect, and this makes the two scrub triggers behave that way.

Tests

Three new specs in test/mini.spec.ts › selector condition type › scrub triggers on a list (repeater pattern). Each builds a 4-item [role="list"] with listContainer and a :nth-child(2n) selector condition:

  • viewProgress with native ViewTimeline: animations are created and played only for items 2 and 4
  • viewProgress with the fizban polyfill: scroll scenes and Scroll instances only for items 2 and 4
  • pointerMove: scrub scenes and Pointer instances only for items 2 and 4

Without the fix, all three fail with expected "vi.fn()" to be called 2 times, but got 4 times. With it, yarn workspace @wix/interact test passes (494/494). tsc --noEmit, eslint and prettier are clean on the changed files.

Additional Notes

  • Why the specs don't use the & predicate: jsdom's selector engine (nwsapi 2.2.23) throws on & in Element.matches() and mis-evaluates :scope there, so the Thunderbolt-shaped predicate can't run in the unit tests. The specs use :nth-child(2n) on the list items, which goes through the same target.matches(selectorCondition) gate. In a real browser (Chrome 152), on four items nested in a [role="list"], item.matches('[role="list"] > :nth-child(2n) &') returns [false, true, false, true].
  • Checked once, at add time: the condition is evaluated when the handler is added. A target that only starts matching later (say, a class toggled at runtime) won't get the scroll or pointer effect until it's added again. Positional predicates such as repeater patterns aren't affected. The event-based handlers re-check on every event, but scrub triggers have no event to check in.
  • Old engines and &: a predicate that uses & depends on the browser resolving a top-level & in matches(), which comes with CSS Nesting support. Engines without it throw a SyntaxError. That was already true for the other handlers, but those throw inside an event callback; here it would throw from add().

🤖 Generated with Claude Code

@eliranhe
eliranhe marked this pull request as ready for review October 21, 2026 14:27
@eliranhe
eliranhe enabled auto-merge (squash) October 22, 2026 06:13
@eliranhe

Copy link
Copy Markdown
Contributor Author

#skipreview

core/add.ts resolves an effect's `selector` condition and passes it to
every trigger handler as `selectorCondition`. click, hover, viewEnter and
animationEnd skip a target that doesn't match it, and the CSS that
generate() writes is scoped by it too. viewProgress and pointerMove
ignored it: every target got the scroll-driven animation or the pointer
effect, so an effect meant for part of a list (e.g. every other repeater
item) ran on all of them.

Return early from both handlers when the target doesn't match. Scrub
effects have no per-event callback to check in, so the check runs once,
when the handler is added, before the animation, scroll scene or Pointer
is created.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@eliranhe
eliranhe force-pushed the fix/scrub-triggers-selector-condition branch from 9c7b0d8 to 5f18c39 Compare October 22, 2026 06:19
@eliranhe
eliranhe merged commit 6e30f33 into master Oct 22, 2026
1 check passed
@eliranhe
eliranhe deleted the fix/scrub-triggers-selector-condition branch October 22, 2026 06:22
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.

2 participants