Skip to content

Idle

Detect "no user interaction for N ms" without wiring pointer/keyboard listeners yourself.

Previous: Focus · Next: Connectivity

Playground

Open Idle playground → — stop interacting and watch session:idle fire after the configured timeout.

Import path

ts
import { createBrowserLifecycle } from "@jayoncode/browser-lifecycle";

Problem → approach

Without idle moduleWith idle events
Manual pointerdown / keydown listeners plus a setTimeoutidleTimeout config + session:idle / session:active events
Debounce logic duplicated per featureBuilt-in activityDebounce (default 250ms)
No shared "is the user idle right now" signalsnapshot.activity"active" | "idle" | "unknown"
ts
const lifecycle = createBrowserLifecycle({ idleTimeout: 30_000 });

lifecycle.on("session:idle", () => pauseAutosave());
lifecycle.on("session:active", () => resumeAutosave());

With Form Intelligence drafts, prefer draft on tab hide for page:hidden; use idle events for longer inactivity UX (lock screen, pause remote sync).

Off by default

idleTimeout defaults to false — the Idle module is disabled until you set an explicit millisecond timeout.

Pitfalls

  • idleTimeout: false (the default) means no idle detection at all — no timers, no listeners.
  • Idle detection needs its own activityEvents to fire; if you narrow the list too much, real interaction may go unnoticed.
  • Always dispose() the session (or unsubscribe) on unmount — activity listeners are held by the session.

Technical reference

Overview

The Idle Module derives an active / idle activity state from configured DOM interaction events, gated entirely behind the idleTimeout option.

It is responsible only for:

  • listening for configured activity events (debounced)
  • tracking the time since the last activity
  • scheduling an idle timer that fires after idleTimeout of inactivity
  • reporting normalized activity transitions to Session Core
  • cleaning up browser listeners and timers

Configuration

OptionTypeDefaultNotes
idleTimeoutfalse | numberfalsefalse disables the module entirely. A positive integer (ms) enables it.
activityEvents"default" | readonly BrowserLifecycleActivityEventName[]"default"["pointerdown", "keydown", "touchstart", "visibilitychange", "focus"]Non-empty array of allowed activity event names (see below).
activityDebouncenumber (ms)250Debounce applied before an activity signal is recorded.

Allowed activityEvents values (BrowserLifecycleActivityEventName): "focus", "keydown", "mousedown", "mousemove", "pointerdown", "pointermove", "touchmove", "touchstart", "visibilitychange".

ts
const lifecycle = createBrowserLifecycle({
  idleTimeout: 60_000,
  activityEvents: ["pointerdown", "keydown"],
  activityDebounce: 500,
});

visibilitychange is observed on document; every other activity event is observed on window.

Architecture

text
activity DOM events
  -> activity adapter
  -> Idle Module (debounce + idle timer)
  -> Session Core internal event
  -> public BrowserLifecycle event

Implementation is split into:

  • src/modules/idle/activity-adapter.ts
  • src/modules/idle/idle-module.ts
  • src/modules/idle/types.ts
  • src/modules/idle/index.ts

Session Integration

The Idle Module reports changes to Session Core through internal:activity-detected, internal:activity-reset, and internal:activity-changed. Session Core then:

  • updates snapshot.activity
  • records event timestamps
  • emits the corresponding public event
  • preserves lifecycle-first ordering by flushing startup activity events after session:started

Public Events

EventFires whenMetadata
activity:detectedAn activity event clears the debounce window (regardless of current state)activitySource — the triggering event name
activity:resetActivity is recorded while already active (idle timer restarted, no transition)activitySource
session:activeActivity is recorded while idle — transition back to activeactivitySource, idleDuration? (ms since last activity)
session:idleNo activity for idleTimeout msidleTimeout, lastActivityAt
ts
lifecycle.on("session:idle", (event) => {
  console.log(event.metadata?.idleTimeout, event.metadata?.lastActivityAt);
});

lifecycle.on("session:active", (event) => {
  console.log(event.metadata?.idleDuration);
});

Snapshot field

ts
const { activity } = lifecycle.getSnapshot();
// "active" | "idle" | "unknown"

Starts "active" as soon as the module initializes (when enabled); stays "unknown" when disabled or unsupported.

SSR Safety

Construction remains SSR-safe:

  • no window / document access during Session Core construction
  • browser APIs are touched only through the adapter during module initialization and start

If activity observation is unavailable for the configured events, the module disables itself without throwing.

Browser Compatibility

This module depends on:

  • idleTimeout being set to a positive integer (it is false by default)
  • the idle capability (supportsIdle()window with addEventListener/removeEventListener and a document)
  • every configured activityEvents entry resolving to a valid target (window or document)

When any of these are unmet:

  • snapshot.activity remains "unknown"
  • no activity listeners or idle timers are attached
  • no activity/idle events are emitted

Connectivity →

An ecosystem of independent, headless TypeScript libraries engineered for modern web development. Every package includes interactive playgrounds and documentation that evolves alongside the code.