DEVELOPER DOCUMENTATION

Vue API reference

Provider, Composition API composable, scoped wrapper, callbacks, and shared guard options for Vue 3.

COMPONENT · PROVIDER

ActivationGuardProvider

Provides one guard to descendant components and attaches it to the rendered subtree after mount. When root is omitted, the provider creates a display: contents wrapper; pass an element or document as root to attach to an existing boundary.

Changing a guard configuration option recreates the guard and resets in-flight interaction observations. Keep configuration stable during a press; callback changes are picked up without recreating the guard.

<script setup lang="ts">
import { ActivationGuardProvider, type Decision } from '@sparshlabs/vue'

function handleDecision(decision: Decision) {
  if (!decision.allowed) {
    console.info('Suspicious activation:', decision.reason)
  }
}
</script>

<template>
  <ActivationGuardProvider
    mode="enforce"
    :cooldown-ms="500"
    :on-decision="handleDecision"
  >
    <ApplicationShell />
  </ActivationGuardProvider>
</template>

In Vue templates, bind callback props using kebab-case names such as :on-decision and :on-blocked. In render functions, use the camelCase prop names.

Props

PropTypeDefault / behavior
default slotVue slotSubtree to observe.
modeMode | Partial<Record<PolicyId, Mode>>'enforce'. Unspecified policies in a mode map remain enforced.
policiesPolicyId[]All four policies by default.
cooldownMsnumber500. Age policy threshold in milliseconds.
rectThresholdPxnumber4. Continuity movement tolerance.
refractoryMsnumber300. DoubleFire recency window.
semanticsTextCheckbooleanfalse. Also compare target text.
clickSuppressMsnumber500. Matching native click window.
onDecision(decision: Decision) => voidCalled for allowed and suspicious decisions.
onSuspect(decision: Decision) => voidCalled when a policy verdict is suspicious, including report mode.
onBlocked(element: Element) => voidCalled after an enforced activation is canceled; replaces the default shake.
blockAnimationbooleantrue. Enables the default shake when no custom callback is set.
blockAnimationMsnumber300. Default shake duration.
rootElement | DocumentOptional explicit DOM root.

COMPOSABLE · ELEMENT FEEDBACK

useActivationGuard()

Call the composable from a descendant component’s setup() or <script setup>. It returns a callback ref and a reactive Ref<boolean> that becomes true when the element is actually blocked.

<script setup lang="ts">
import { useActivationGuard } from '@sparshlabs/vue'

const { ref: guardRef, isGuarded } = useActivationGuard()
</script>

<template>
  <button :ref="guardRef" type="button">Save draft</button>
  <p v-if="isGuarded" role="status">
    The screen changed. Try again.
  </p>
</template>
  • The returned ref is a callback: (node: Element | null) => void.
  • isGuarded is automatically unwrapped in templates and resets after an allowed activation.
  • Without a provider ancestor, the composable is a no-op and does not throw.
  • Options API direct support is not included; use a Composition API component as the descendant.

COMPONENT · SCOPED GUARD

ActivationGuard

This is an alias of ActivationGuardProvider with the same props and slots. A nested instance installs a separate guard on its subtree; options are not merged with an ancestor.

<ActivationGuard :mode="{ age: 'report' }">
  <FrequentlyUpdatedPanel />
</ActivationGuard>

Nesting providers: each provider installs its own capture-phase listeners and policy pipeline — nothing is merged or inherited from an ancestor. When providers overlap the same DOM, the outermost one sees pointer/click/key events first and, if it blocks, the inner provider never sees that interaction at all. This is exactly how <ActivationGuard> is meant to be used for a scoped override, but accidentally wrapping the same subtree with two top-level ActivationGuardProviders is usually a mistake. sparsh logs a console.warn whenever a new guard's root overlaps one that's already active, so you can tell the two cases apart — ignore it for an intentional scoped override, fix it otherwise.

BLOCK FEEDBACK

Customize the blocked animation

onBlocked runs after cancellation and receives the interactive element. A callback exception is ignored so it cannot affect the guard’s decision.

<script setup lang="ts">
import { ActivationGuardProvider } from '@sparshlabs/vue'

function animateBlocked(element: Element) {
  element.animate(
    [
      { transform: 'scale(1)' },
      { transform: 'scale(0.96)' },
      { transform: 'scale(1)' },
    ],
    { duration: 220, easing: 'ease-out' },
  )
}
</script>

<template>
  <ActivationGuardProvider :on-blocked="animateBlocked">
    <ApplicationShell />
  </ActivationGuardProvider>
</template>

RELATED TYPES

Decision and mode

Mode, PolicyId, and Decision are re-exported from @sparshlabs/vue. A decision’s allowed field records the policy verdict; enforced says whether it actually stopped the activation. See Core concepts for policy behavior and accessibility details.

import type { InputKind, Phase, PolicyId } from '@sparshlabs/vue'
import type { Ref } from 'vue'

export interface UseActivationGuardResult {
  ref: (node: Element | null) => void
  isGuarded: Ref<boolean>
}

export interface Decision {
  allowed: boolean
  enforced: boolean
  policy?: PolicyId
  reason?: string
  event: { kind: InputKind; phase: Phase }
  target: {
    identity: unknown
    ageMs: number
    guardKey?: string
  }
}