tiny-event-intercept
v2.0.0
Published
Lightweight (~1.2KB gzip) TypeScript library for conditional event interception with browser-standard API. Zero dependencies, browser-first, SSR-safe.
Maintainers
Readme
tiny-event-intercept
A lightweight (~1.2KB gzip), zero-dependency TypeScript library for conditional event interception with browser-standard API. Designed for modern web apps and safe to call in SSR or other non-browser environments.
Languages: English | 简体中文
Features
- 🔒 Type Safe: Full TypeScript support with strict typing and IntelliSense
- 🌐 Browser-First: Works in browsers and safely returns a noop cleanup outside the browser
- 🧹 Predictable Cleanup: Explicit cleanup with
AbortSignalsupport - ⚡ Performance: Optimized event handling with minimal overhead (~700K ops/sec)
- 📦 Lightweight: About ~1.2KB gzip for the runtime bundle, zero dependencies
- 🎯 Browser Standard: Extends native
AddEventListenerOptionsAPI - 🔄 Framework Agnostic: Works with React, Vue, Svelte, or vanilla JavaScript
Installation
npm install tiny-event-interceptQuick Start
import { interceptEvents } from 'tiny-event-intercept'
// Prevent clicks when feature is disabled
let isFeatureEnabled = false
const cleanup = interceptEvents(document, {
events: 'click',
when: () => !isFeatureEnabled, // Only intercept when feature is disabled
listener: (event) => {
console.log('Feature is currently disabled')
event.preventDefault()
event.stopPropagation()
},
})
// Enable feature later
isFeatureEnabled = true
// Clean up when done (removes all event listeners)
cleanup()API
interceptEvents(target, options): CleanupFunction
Creates conditional event interceptors with a browser-standard API.
function interceptEvents(target: TargetElement, options: InterceptOptions): CleanupFunction
// Types
type TargetElement = EventTarget | (() => EventTarget | null) | null
type EventTypes<K extends keyof GlobalEventHandlersEventMap = keyof GlobalEventHandlersEventMap> =
| K
| readonly K[]
interface InterceptOptions<K extends keyof GlobalEventHandlersEventMap = keyof GlobalEventHandlersEventMap>
extends AddEventListenerOptions {
events: EventTypes<K> // Event types to intercept
when: () => boolean // Condition function
listener: (event: GlobalEventHandlersEventMap[K]) => void // Event handler
// Inherits: capture?, once?, passive?, signal?
}
type EventTarget = Element | Document | Window
type CleanupFunction = () => voidParameters:
target- Target element, function returning element, or null (defaults to document)options- Intercept options including events, condition, and listener
Returns:
CleanupFunction- Function to remove all event listeners
Notes:
target === nulldefaults todocument- Function targets are resolved once when
interceptEvents()is called - If a target resolver returns
nullor throws, no listeners are attached - Use
as constfor event arrays if you want the narrowest union type inlistener
Real-World Use Cases
1. Form Validation Blocking
const submitButton = document.querySelector('#submit-btn')
let isFormValid = false
const cleanup = interceptEvents(submitButton, {
events: 'click',
when: () => !isFormValid,
listener: (event) => {
event.preventDefault()
showValidationErrors()
console.log('Form submission blocked - validation failed')
},
})2. Loading State Management
let isLoading = false
const cleanup = interceptEvents(document, {
events: ['click', 'keydown', 'submit'],
when: () => isLoading,
listener: (event) => {
event.preventDefault()
event.stopPropagation()
showLoadingMessage('Please wait...')
},
capture: true, // Intercept in capture phase for better control
})3. Modal/Dialog Interaction Control
let isModalOpen = false
const cleanup = interceptEvents(document, {
events: 'keydown',
when: () => isModalOpen,
listener: (event) => {
if (event.key === 'Escape') {
closeModal()
event.preventDefault()
}
},
})4. Feature Flag Implementation
const featureButton = document.querySelector('#new-feature-btn')
const cleanup = interceptEvents(featureButton, {
events: 'click',
when: () => !window.featureFlags?.newFeatureEnabled,
listener: (event) => {
event.preventDefault()
showFeatureNotAvailable()
},
})5. Lazy Target Resolution
// Resolve the active tab once when registering listeners
const cleanup = interceptEvents(() => document.querySelector('.tab.active'), {
events: 'click',
when: () => isTabSwitchingDisabled,
listener: (event) => {
event.preventDefault()
showMessage('Tab switching is temporarily disabled')
},
})6. Advanced Options Usage
const controller = new AbortController()
const cleanup = interceptEvents(document.body, {
events: ['mousedown', 'touchstart'],
when: () => isDragModeActive,
listener: (event) => {
startDragOperation(event)
},
capture: true, // Capture phase for early interception
passive: false, // Allow preventDefault()
signal: controller.signal, // AbortController support
})
// Later: abort all listeners
controller.abort()Framework Integration
React
import { useEffect, useState } from 'react'
import { interceptEvents } from 'tiny-event-intercept'
function FeatureToggle() {
const [isEnabled, setIsEnabled] = useState(false)
useEffect(() => {
const cleanup = interceptEvents(document, {
events: 'click',
when: () => !isEnabled,
listener: (event) => {
console.log('Feature disabled')
event.preventDefault()
}
})
return cleanup // Cleanup on unmount
}, [isEnabled])
return (
<button onClick={() => setIsEnabled(!isEnabled)}>
{isEnabled ? 'Disable' : 'Enable'} Feature
</button>
)
}Vue
import { onMounted, onUnmounted, ref } from 'vue'
import { interceptEvents } from 'tiny-event-intercept'
export default {
setup() {
const isEnabled = ref(false)
let cleanup: (() => void) | null = null
onMounted(() => {
cleanup = interceptEvents(document, {
events: 'click',
when: () => !isEnabled.value,
listener: (event) => event.preventDefault(),
})
})
onUnmounted(() => {
cleanup?.()
})
return { isEnabled }
},
}Memory Management
The library provides predictable cleanup mechanisms:
- Manual cleanup: Call the returned cleanup function
- AbortSignal support: Native listener cleanup via
signal - Idempotent: Safe to call cleanup multiple times
const cleanup = interceptEvents(document, {
events: 'click',
when: () => true,
listener: () => {},
})
// Safe to call multiple times
cleanup()
cleanup() // No errorsLicense
MIT
