Skip to content

Mount strategies

A mount strategy decides when the registry mounts an instance. It constructs nothing and it wraps nothing.

The seven values

StrategyMounts whenReversible
eager (default)the element enters the documentno
visible[:<rootMargin>]it first intersects the viewportno
in-view[:<rootMargin>]it intersects the viewportyes
idlethe main thread becomes idleno
interactionthe user first aims at itno
interaction:pagethe user first interacts with the pageno
media:<query>the query is not empty and it matchesyes

One-shot and reversible are separate values. visible mounts once and stays. in-view mounts and unmounts on each crossing.

Declaring one

A component declares its default in its config; any element overrides it with data-mount:

js
import { 
Base
} from '@studiometa/js-toolkit';
class
Map
extends
Base
{
static
config
= {
name
: 'Map',
mountStrategy
: 'visible',
}; }
html
<!-- uses the component default -->
<div data-component="Map"></div>

<!-- overridden per element -->
<div data-component="Map" data-mount="eager"></div>
<div data-component="Map" data-mount="visible:200px"></div>
<div data-component="Chat" data-mount="interaction:page"></div>
<div data-component="Parallax" data-mount="media:(prefers-reduced-motion: no-preference)"></div>

The resolution chain is:

data-mount  >  manifest entry mountStrategy  >  config.mountStrategy  >  'eager'

The grammar

The accepted values are exactly eager, visible, visible:, visible:<rootMargin>, in-view, in-view:, in-view:<rootMargin>, idle, interaction, interaction:, interaction:page and media:<non-empty query>.

  • A viewport suffix is passed straight through as IntersectionObserverInit.rootMargin.
  • The empty suffix behaves as the bare strategy.
  • page is the only scope the interaction strategy takes.
  • There is no threshold, no root element, no JSON options and no second attribute.

An unknown value, an empty media query, or a rootMargin the browser refuses leaves the component unmounted and reports component.invalid-mount-strategy once. The inert controller stays current while the declaration does not change, so it is not reported again; a corrected attribute replaces it. A failed strategy cannot stop the rest of a reconciliation.

interaction — intent on the element

pointerenter, pointerdown and focusin, bound to the element itself, so the component mounts before the click arrives. Two of the three bubble, so an interaction anywhere inside the subtree counts; pointerenter fires for the root element only.

interaction:page — the visit, not the element

This is the deferred-widget case: a chat panel, an embed, a third-party script, where nothing about the element predicts the moment and any sign of a live user does.

  • The events are the deliberate ones: pointerdown, keydown and focusin. Hovering is deliberately not one of them — aiming is intent for one element and noise for a document.
  • One listener set for the page, in the shared runtime, not one per waiting element. They attach with the first waiting element and release on the first interaction.
  • They are captured, so a handler that stops propagation cannot hide the visit.
  • The signal is a fact about the visit. An element that arrives after the interaction mounts at once — but only when the strategy was already listening, since nothing observes a page that has asked for nothing.

What a waiting component is

A component that waits has no instance. It is invisible to $query(), $closest(), $watchChildren() and getInstances(), and it announces nothing. There is nothing to guard against and nothing to check.

Once it has mounted and been stood down again — which only in-view and media: do — the instance exists and is unmounted, so it is what getUnmountedInstances() answers.

Lifetime

  • Several components on one element share that element's data-mount. A component that needs its own policy declares it in its config.
  • Teardown follows the element. A strategy is disposed when its element leaves the document. A move ends as an unmount and a mount of the same instance.
  • The attribute is live. A change to data-mount disposes the old strategy and applies the final one. Controller identity guards a queued callback of a disposed strategy.

What this replaces

The withMountWhenInView, withMountOnMediaQuery and withMountWhenPrefersMotion decorators of v3 are deleted. A decorator that wraps a constructor to decide whether to mount is the registry's job, and the registry does it without touching the class.

data-load and loadStrategy are gone too: deferring the import and deferring the mount are one decision. See Autoloading.

withInView is a different thing

withInView observes a component that is already mounted and calls intersected(). It does not replace the visible or in-view mount strategy.

MIT Licensed