Skip to content
LaravelBlog

livewire/livewire: chore: remove unset static::$current from LivewireManager

LaravelBlogBot
LaravelBlogBot

Contributed by cyppe

Part of the livewire v4.4.7 release

Pull request #10599 gives wire:intersect a sense of time. Until now the directive could tell you when an element crossed a visibility threshold, but not whether it stayed there — so a fast scroll, a late layout shift, or a backgrounded tab could all register as a "view." The change adds a .dwell modifier that requires an element to remain continuously visible for a configurable duration before your action fires. Because wire:intersect forwards its modifiers straight to Alpine's x-intersect, the Livewire side of this PR is documentation plus a new browser contract suite; the dwell state machine itself lives in the @alpinejs/intersect plugin via a companion Alpine pull request. The PR is a draft, deliberately sequenced to merge only after that dependency lands.

Why a threshold alone isn't enough

Intersection triggers are edge-triggered: they report the moment an element crosses .half, .full, or a custom .threshold. That's the wrong shape for work that assumes attention. Impression tracking, deferred media loading, preview playback, and reading-progress signals all want an element to be viewed, not merely passed. Reproducing that today means application-owned IntersectionObserver plus timeout bookkeeping, with reset logic for scroll jank and tab visibility. .dwell turns that whole pattern into a single declarative modifier, so qualified impressions and visibility-gated actions read naturally in Blade with no JavaScript in your app.

What .dwell does

The default dwell interval is 250 milliseconds, and a duration modifier selects your own:

<!-- Fires only after the element stays at least half visible for 500ms -->
<div wire:intersect.once.half.dwell.500ms="trackImpression">
    ...
</div>

The timer resets if the element drops below its visibility threshold or the page moves to the background, then restarts once the element becomes eligible in a visible page. Combined with .once, that's a one-shot, verified impression.

Leave actions use the same vocabulary, with the dwell interval starting after visibility falls below the threshold:

<video wire:intersect:leave.half.dwell.750ms="pausePreview">...</video>

Here the preview survives brief scroll-pasts and only pauses once the video has genuinely been out of view for 750 milliseconds.

A thin bridge, proven by tests

The Livewire directive already forwards every modifier to Alpine, so duplicating the dwell logic in Livewire would create two competing implementations. Instead, this PR adds a focused browser contract that proves three things end to end: the complete modifier suffix (.once.parent.dwell.1000ms) reaches Alpine intact; dwell initializes on elements added during a morph, not just on initial page load; and dwell works with .renderless actions, running the action without triggering a component render. The docs gain a "Require continuous visibility" section in wire-intersect.md, and the upgrade guide's modifier tables now document .dwell.[duration] alongside newly listed .parent, .renderless, .async, and .preserve-scroll options.

The Alpine dependency, and when this ships

.dwell is implemented in alpinejs/alpine#4884. Livewire 4.x currently installs @alpinejs/intersect 3.16.3, which has no dwell support, so the feature becomes available only after that Alpine PR merges and the usual published dependency bump lands in a Livewire release. The PR intentionally contains no fork reference, no unpublished package pin, and no duplicated JavaScript — which is why it's held as a draft. Its own validation ran against a locally linked Alpine feature branch: 3 browser tests with 6 assertions, the full JavaScript suite (110 passed, 1 skipped), and the PHP suite (1,391 tests, 3,964 assertions), plus a passing production asset build.

Upgrading from v3.8.10

There are no breaking changes and no migration steps. Your existing wire:intersect, wire:intersect:leave, .once, .half, .full, .threshold, and .margin usage behaves exactly as before; .dwell is purely opt-in and only applies where you write it. One caveat if you share Blade templates across major versions: on Livewire 3 (including 3.8.10) the .dwell modifier has no implementation, so an action written as wire:intersect.dwell.500ms="track" fires as soon as the threshold is crossed rather than after the dwell. Don't rely on that timing until you're on a Livewire 4.x release that bundles the updated Alpine — and nothing changes until you explicitly adopt the new modifier.

  • .dwell makes wire:intersect require continuous visibility (default 250ms, e.g. .dwell.500ms) before firing; it resets on threshold loss or a backgrounded tab.
  • It works on enter and leave actions and composes with .once, .half, .full, .threshold, .parent, and .renderless.
  • The dwell logic ships in Alpine's intersect plugin (PR #4884); Livewire contributes documentation, a browser contract suite, and no duplicated implementation.
  • Available in Livewire 4.x only after the Alpine PR merges and the dependency bump is published; this PR stays draft until then.
  • Upgrading from v3.8.10: nothing breaks, nothing to migrate — .dwell is opt-in and inert on v3.

Sources

More from this release

Related Articles