ZogKit
Essential utilities plugin for Zog.js

ZogKit is a comprehensive utilities plugin that extends Zog.js with powerful features including HTTP client, storage helpers, DOM directives, event bus, and performance utilities.
📦 Installation
npm install @zogjs/kit
🚀 Quick Start
import { createApp, ref } from './zog.js';
import ZogKit from './zog-kit.js';
const kit = createApp(() => {
const message = ref('Hello ZogKit!');
return { message };
}).use(ZogKit, {
baseURL: 'https://api.example.com',
timeout: 30000,
storagePrefix: 'myapp_'
});
kit.mount('#app');
⚠️ Important: How ZogKit Works
Plugin API Access
When you call .use(ZogKit), it returns an object containing all plugin APIs:
const kit = app.use(ZogKit);
Two common patterns:
Pattern 1: Chain everything
const kit = createApp(() => {
async function loadData() {
const { data } = await kit.$http.get('/api/data');
return data;
}
return { loadData };
}).use(ZogKit, { baseURL: 'https://api.example.com' });
kit.mount('#app');
Pattern 2: Separate declarations
const app = createApp(() => {
async function loadData() {
const { data } = await kit.$http.get('/api/data');
return data;
}
return { loadData };
});
const kit = app.use(ZogKit, { baseURL: 'https://api.example.com' });
app.mount('#app');
Understanding Refs in Templates vs JavaScript
In templates (auto-unwrapped):
<p>{{ count }}</p>
<button :disabled="isLoading">Submit</button>
In JavaScript (use .value):
count.value++;
if (isLoading.value) { }
In z-for loops (items are ALWAYS refs):
<div z-for="user in users">
<p>{{ user.name }}</p>
<button @click="selectUser(user)">Select</button>
</div>
function selectUser(user) {
console.log(user.value.name);
}
📚 Features
HTTP Client (kit.$http)
A powerful HTTP client with automatic JSON handling, timeout support, and request abortion.
Storage Helpers (kit.$storage, kit.$session)
Enhanced localStorage and sessionStorage with TTL support and reactive state.
DOM Directives
z-pre - Skip compilation
z-once - Render once without reactivity
z-cloak - Hide until compiled
z-autofocus - Auto focus elements
z-click-outside - Detect outside clicks
z-lazy - Lazy load images
z-copy - Copy to clipboard
Event Bus (kit.$bus)
Global event system for component communication.
Performance Utilities (kit.utils)
debounce - Debounce function calls
throttle - Throttle function calls
Clipboard Helper (kit.$clipboard)
Easy-to-use clipboard operations with fallback support.
📖 Detailed Documentation
Configuration Options
const kit = app.use(ZogKit, {
baseURL: '',
timeout: 30000,
headers: {},
storagePrefix: 'zog_',
storageTTL: null
});
🌐 HTTP Client
The kit.$http service provides a clean API for making HTTP requests.
Basic Usage
import { createApp, ref, watchEffect } from './zog.js';
import ZogKit from './zog-kit.js';
const kit = createApp(() => {
const users = ref([]);
const loading = ref(false);
const error = ref(null);
async function loadUsers() {
loading.value = true;
error.value = null;
try {
const { data } = await kit.$http.get('/users');
users.value = data;
} catch (err) {
error.value = err.message;
} finally {
loading.value = false;
}
}
async function createUser(userData) {
const { data } = await kit.$http.post('/users', userData);
users.value.push(data);
}
async function updateUser(id, userData) {
await kit.$http.put(`/users/${id}`, userData);
}
async function deleteUser(id) {
await kit.$http.delete(`/users/${id}`);
}
watchEffect(() => {
loadUsers();
});
return { users, loading, error, createUser, updateUser, deleteUser };
}).use(ZogKit, { baseURL: 'https://api.example.com' });
kit.mount('#app');
Advanced Usage
const { data } = await kit.$http.get('/api/data', {
headers: {
'Authorization': 'Bearer token123'
}
});
const response = await kit.$http.post('/api/upload', formData, {
timeout: 60000
});
const { data, response, status } = await kit.$http.get('/users');
console.log(status);
console.log(response.headers);
FormData Support
const kit = createApp(() => {
async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
await kit.$http.post('/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
}
return { uploadFile };
}).use(ZogKit);
💾 Storage Helpers
Enhanced storage with TTL support and reactive state.
LocalStorage (kit.$storage)
import { createApp } from './zog.js';
import ZogKit from './zog-kit.js';
const kit = createApp(() => {
kit.$storage.set('user', { name: 'John', age: 30 });
const user = kit.$storage.get('user');
const theme = kit.$storage.get('theme', 'light');
kit.$storage.set('token', 'abc123', 5 * 60 * 1000);
if (kit.$storage.has('token')) {
console.log('Token exists');
}
function logout() {
kit.$storage.remove('token');
}
function clearAll() {
kit.$storage.clear();
}
return { logout, clearAll };
}).use(ZogKit, { storagePrefix: 'myapp_' });
SessionStorage (kit.$session)
kit.$session.set('tempData', { foo: 'bar' });
const data = kit.$session.get('tempData');
Reactive Storage
Create reactive state that automatically syncs with storage:
import { createApp } from './zog.js';
import ZogKit from './zog-kit.js';
const kit = createApp(() => {
const theme = kit.$storage.reactive('theme', 'light');
function toggleTheme() {
theme.value = theme.value === 'light' ? 'dark' : 'light';
}
return { theme, toggleTheme };
}).use(ZogKit);
kit.mount('#app');
<div id="app">
<p>Current theme: {{ theme }}</p>
<button @click="toggleTheme">Toggle Theme</button>
</div>
🎯 DOM Directives
z-pre
Skip Zog compilation entirely for static content:
<div z-pre>
{{ This will not be compiled }}
<span :class="notReactive">Static content</span>
</div>
z-once
Render once without reactivity (improves performance for static data):
<div z-once>
<h1>{{ user.name }}</h1>
<p>{{ user.bio }}</p>
</div>
Use case: Display data that won't change after initial render.
z-cloak
Hide element until compilation is complete (prevents flash of uncompiled content):
<div z-cloak>
{{ message }}
</div>
Automatically adds CSS rule: [z-cloak] { display: none !important; }
z-autofocus
Automatically focus an element when it appears:
<input z-autofocus type="text" placeholder="Auto-focused">
<input z-if="showModal" z-autofocus type="text">
z-click-outside
Detect clicks outside an element (perfect for dropdowns, modals):
import { createApp, ref } from './zog.js';
const kit = createApp(() => {
const isOpen = ref(false);
function close() {
isOpen.value = false;
}
return { isOpen, close };
}).use(ZogKit);
<div z-click-outside="close" class="dropdown">
<button @click="isOpen = !isOpen">Toggle</button>
<ul z-show="isOpen">
<li>Option 1</li>
<li>Option 2</li>
</ul>
</div>
z-lazy
Lazy load images when they enter the viewport:
<img z-lazy="https://example.com/large-image.jpg" alt="Lazy loaded">
Benefits:
- Faster initial page load
- Reduced bandwidth usage
- Automatic IntersectionObserver integration
z-copy
Copy text to clipboard on click:
import { createApp, ref } from './zog.js';
const kit = createApp(() => {
const code = ref('npm install @zogjs/kit');
function showNotification() {
console.log('Copied!');
}
return { code, showNotification };
}).use(ZogKit);
<button z-copy="code">Copy Code</button>
<button z-copy="code" @copied="showNotification">Copy</button>
🎭 Event Modifiers
Important: Event Modifier Support
ZogKit adds custom modifiers (.debounce, .throttle) to Zog.js:
<input @input.debounce.500="search">
<div @scroll.throttle.1000="handleScroll">
<form @submit.prevent="onSubmit">
<input @keyup.enter="search">
<button @click.stop="onClick">
Workaround for standard modifiers:
const kit = createApp(() => {
function onSubmit(e) {
e.preventDefault();
e.stopPropagation();
}
function onKeyup(e) {
if (e.key === 'Enter') {
search();
}
}
return { onSubmit, onKeyup };
}).use(ZogKit);
Debounce
Delay function execution until after wait time has elapsed:
<input @input.debounce.500="search" type="text">
<input @keyup.debounce="handleInput">
Important: The handler must exist in scope:
const kit = createApp(() => {
const query = ref('');
function search() {
console.log('Searching for:', query.value);
}
return { query, search };
}).use(ZogKit);
Use case: Search inputs, form validation
Throttle
Execute function at most once per specified time period:
<div @scroll.throttle.1000="handleScroll">Scrollable content</div>
<button @click.throttle="saveData">Save</button>
Use case: Scroll handlers, resize handlers, rapid button clicks
📢 Event Bus
Global event system for component communication.
Basic Usage
const kit = createApp(() => {
function login(userId) {
kit.$bus.emit('user-login', { userId });
}
kit.$bus.on('user-login', (data) => {
console.log('User logged in:', data.userId);
});
return { login };
}).use(ZogKit);
Complete API
import { createApp, watchEffect } from './zog.js';
import ZogKit from './zog-kit.js';
const kit = createApp(() => {
const unsubscribe = kit.$bus.on('message', (data) => {
console.log('Received:', data);
});
function cleanup() {
unsubscribe();
kit.$bus.off('message', handler);
}
kit.$bus.once('init', () => {
console.log('Initialized once');
});
function sendMessage(text) {
kit.$bus.emit('message', { text });
}
function clearMessages() {
kit.$bus.clear('message');
}
function clearAll() {
kit.$bus.clear();
}
return { sendMessage, cleanup, clearMessages, clearAll };
}).use(ZogKit);
Real-world Example
import { createApp, ref, reactive } from './zog.js';
import ZogKit from './zog-kit.js';
const notificationApp = createApp(() => {
const notifications = reactive([]);
notificationKit.$bus.on('notify', (message) => {
notifications.push(message);
setTimeout(() => notifications.shift(), 3000);
});
return { notifications };
}).use(ZogKit);
const notificationKit = notificationApp;
const userApp = createApp(() => {
async function saveUser(userData) {
try {
await userKit.$http.post('/users', userData);
userKit.$bus.emit('notify', {
type: 'success',
text: 'User saved successfully!'
});
} catch (error) {
userKit.$bus.emit('notify', {
type: 'error',
text: 'Failed to save user'
});
}
}
return { saveUser };
}).use(ZogKit);
const userKit = userApp;
📋 Clipboard Helper
Copy text to clipboard with automatic fallback.
const kit = createApp(() => {
const message = ref('');
async function copyToClipboard(text) {
const success = await kit.$clipboard.copy(text);
if (success) {
message.value = 'Copied!';
} else {
message.value = 'Copy failed';
}
}
return { message, copyToClipboard };
}).use(ZogKit);
Features:
- Modern Clipboard API with fallback
- Automatic error handling
- Works in all browsers
⚡ Performance Utilities
Debounce Function
import { createApp, ref } from './zog.js';
import ZogKit from './zog-kit.js';
const kit = createApp(() => {
const query = ref('');
const search = kit.utils.debounce(function() {
kit.$http.get('/search?q=' + query.value);
}, 500);
return { query, search };
}).use(ZogKit);
Throttle Function
import { createApp, ref, watchEffect } from './zog.js';
import ZogKit from './zog-kit.js';
const kit = createApp(() => {
const scrollPosition = ref(0);
const handleScroll = kit.utils.throttle(function() {
scrollPosition.value = window.scrollY;
}, 100);
watchEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
});
return { scrollPosition };
}).use(ZogKit);
🔧 Complete Example
Here's a comprehensive example using multiple ZogKit features:
import { createApp, ref, reactive, watchEffect } from './zog.js';
import ZogKit from './zog-kit.js';
const kit = createApp(() => {
const users = ref([]);
const loading = ref(false);
const searchQuery = ref('');
const isDropdownOpen = ref(false);
const selectedUser = ref(null);
watchEffect(() => {
loadUsers();
});
watchEffect(() => {
const cleanup = kit.$bus.on('user-updated', loadUsers);
return cleanup;
});
const lastSearch = kit.$storage.get('lastSearch', '');
searchQuery.value = lastSearch;
async function loadUsers() {
loading.value = true;
try {
const { data } = await kit.$http.get('/api/users');
users.value = data;
} catch (error) {
kit.$bus.emit('notify', {
type: 'error',
text: 'Failed to load users'
});
} finally {
loading.value = false;
}
}
const search = kit.utils.debounce(async function(query) {
kit.$storage.set('lastSearch', query);
try {
const { data } = await kit.$http.get(`/api/search?q=${query}`);
users.value = data;
} catch (error) {
console.error('Search failed:', error);
}
}, 500);
function closeDropdown() {
isDropdownOpen.value = false;
}
async function copyUserId(user) {
const id = user.value.id;
const success = await kit.$clipboard.copy(id);
if (success) {
kit.$bus.emit('notify', {
type: 'success',
text: 'User ID copied!'
});
}
}
return {
users,
loading,
searchQuery,
isDropdownOpen,
selectedUser,
loadUsers,
search,
closeDropdown,
copyUserId
};
}).use(ZogKit, {
baseURL: 'https://api.example.com',
timeout: 30000,
storagePrefix: 'myapp_'
});
kit.mount('#app');
<div id="app" z-cloak>
<input
z-autofocus
z-model="searchQuery"
@input="search(searchQuery)"
type="text"
placeholder="Search users..."
>
<div z-if="loading">Loading...</div>
<div z-else>
<div z-for="user in users" :key="user.id">
<img z-lazy="user.avatar" :alt="user.name">
<div z-once>
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
</div>
<button @click="copyUserId(user)">Copy ID</button>
</div>
</div>
<div z-click-outside="closeDropdown" class="dropdown">
<button @click="isDropdownOpen = !isDropdownOpen">Options</button>
<ul z-show="isDropdownOpen">
<li>Option 1</li>
<li>Option 2</li>
</ul>
</div>
</div>
🎨 Best Practices
1. Use z-once for Static Content
<div z-once>
<h1>{{ user.name }}</h1>
<p>{{ user.bio }}</p>
</div>
<div z-once>
<p>Count: {{ count }}</p>
</div>
2. Combine z-cloak with Loading States
<div z-cloak>
<div z-if="loading">Loading...</div>
<div z-else>{{ content }}</div>
</div>
3. Use Method Handlers Instead of Inline Expressions
<button @click="count.value++">Increment</button>
<button @click="increment">Increment</button>
function increment() {
count.value++;
}
4. Handle z-for Items Correctly
function selectUser(user) {
console.log(user.value.name);
selectedUser.value = user.value;
}
5. Use Appropriate Debounce/Throttle Delays
<input @input.debounce.500="search">
<div @scroll.throttle.100="onScroll">
6. Clean Up Event Listeners
import { createApp, watchEffect } from './zog.js';
const kit = createApp(() => {
watchEffect(() => {
const unsubscribe = kit.$bus.on('event', handler);
return unsubscribe;
});
return { };
}).use(ZogKit);
7. Use Storage with TTL for Sensitive Data
kit.$storage.set('authToken', token, 60 * 60 * 1000);
🛠 Troubleshooting
HTTP Requests Not Working
- Check if
baseURL is configured correctly
- Verify CORS headers on your API
- Check browser console for errors
- Ensure you're using
kit.$http, not this.$http
z-click-outside Not Triggering
- Ensure the element is in the DOM
- Check if click event is bubbling properly
- Verify the function exists in the returned scope object
Storage Not Persisting
- Check if localStorage is available
- Verify storage quota isn't exceeded
- Check browser privacy settings
- Ensure storagePrefix is set correctly
z-autofocus Not Working
- Ensure element is focusable (input, textarea, button, etc.)
- Check if element is visible (z-if/z-show)
- Verify no conflicting autofocus attributes
Refs Not Working in Templates
<p>{{ count.value }}</p>
<p>{{ count }}</p>
Event Handlers Not Finding Functions
const kit = createApp(() => {
function myHandler() { }
return { };
}).use(ZogKit);
const kit = createApp(() => {
function myHandler() { }
return { myHandler };
}).use(ZogKit);
📄 API Reference
kit.$http
get(url, options) | url: string, options: object | Promise | GET request |
post(url, body, options) | url: string, body: any, options: object | Promise | POST request |
put(url, body, options) | url: string, body: any, options: object | Promise | PUT request |
delete(url, options) | url: string, options: object | Promise | DELETE request |
kit.$storage / kit.$session
set(key, value, ttl) | key: string, value: any, ttl: number | boolean | Store value |
get(key, defaultValue) | key: string, defaultValue: any | any | Retrieve value |
remove(key) | key: string | void | Remove item |
clear() | - | void | Clear all prefixed items |
has(key) | key: string | boolean | Check if exists |
reactive(key, defaultValue, ttl) | key: string, defaultValue: any, ttl: number | Ref | Reactive storage |
kit.$clipboard
copy(text) | text: string | Promise<boolean> | Copy to clipboard |
kit.$bus
on(event, handler) | event: string, handler: function | function | Listen to event (returns unsubscribe) |
off(event, handler) | event: string, handler: function | void | Remove listener |
emit(event, data) | event: string, data: any | void | Emit event |
once(event, handler) | event: string, handler: function | void | Listen once |
clear(event) | event: string | void | Clear listeners for event |
clear() | - | void | Clear all listeners |
kit.utils
debounce(fn, delay) | fn: function, delay: number | function | Debounced function |
throttle(fn, limit) | fn: function, limit: number | function | Throttled function |
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
📝 License
MIT License - see LICENSE file for details.