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
| Prop | Type | Default / behavior |
|---|---|---|
| default slot | Vue slot | Subtree to observe. |
mode | Mode | Partial<Record<PolicyId, Mode>> | 'enforce'. Unspecified policies in a mode map remain enforced. |
policies | PolicyId[] | All four policies by default. |
cooldownMs | number | 500. Age policy threshold in milliseconds. |
rectThresholdPx | number | 4. Continuity movement tolerance. |
refractoryMs | number | 300. DoubleFire recency window. |
semanticsTextCheck | boolean | false. Also compare target text. |
clickSuppressMs | number | 500. Matching native click window. |
onDecision | (decision: Decision) => void | Called for allowed and suspicious decisions. |
onSuspect | (decision: Decision) => void | Called when a policy verdict is suspicious, including report mode. |
onBlocked | (element: Element) => void | Called after an enforced activation is canceled; replaces the default shake. |
blockAnimation | boolean | true. Enables the default shake when no custom callback is set. |
blockAnimationMs | number | 300. Default shake duration. |
root | Element | Document | Optional 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
refis a callback:(node: Element | null) => void. isGuardedis 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
}
}