board.lifecycle - Component Events
Handle component lifecycle events and state transitions.
Overview
board.lifecycle provides events for:
- Component initialization
- Focus/blur (Interaction Mode)
- Visibility changes (viewport scrolling)
- Reconnection after network loss
- Component destruction
Core Events
ready - Component Initialized
Fired when the component is fully loaded and SDK is ready:
typescript
board.on('ready', () => {
console.log('Component initialized!');
// Safe to access all SDK features
const count = board.storage.get('count', 0);
renderUI(count);
});focus - Enter Interaction Mode
Fired when user clicks on the component (iframe becomes active):
typescript
board.on('focus', () => {
console.log('User started interacting');
// Start animations, enable input
startAnimations();
enableInput();
playBackgroundMusic();
});blur - Exit Interaction Mode
Fired when user clicks outside the component (presses ESC):
typescript
board.on('blur', () => {
console.log('User stopped interacting');
// SDK automatically generates preview
// You can also generate custom preview
pauseAnimations();
disableInput();
stopBackgroundMusic();
});beforeFreeze - Before Preview Generation
Fired just before the component is "frozen" (before blur):
typescript
board.on('beforeFreeze', async () => {
// Generate custom preview
const svg = await generateCustomPreview();
await board.ui.updatePreview(svg);
});destroy - Component Removed
Fired when the component is removed from the board:
typescript
board.on('destroy', () => {
console.log('Component being destroyed');
// Cleanup resources
clearInterval(timerId);
audioElement.pause();
videoElement.pause();
});Connection Events
reconnect - Connection Restored
Fired when WebSocket reconnects after network loss:
typescript
board.on('reconnect', () => {
console.log('Connection restored');
// SDK automatically syncs state
// You can show a notification
board.ui.showToast('Connection restored', { type: 'success' });
});Visibility Events
visibilityChange - Component Scrolled Out
Fired when component leaves/enters viewport:
typescript
board.on('visibilityChange', (isVisible: boolean) => {
if (!isVisible) {
// Component scrolled out of view
// Pause heavy operations
pauseWebGLAnimation();
stopVideoPlayback();
console.log('Component scrolled out of view');
} else {
// Component back in view
resumeWebGLAnimation();
resumeVideoPlayback();
console.log('Component back in view');
}
});
// Get current visibility state
const isVisible = board.lifecycle.isVisible;Examples
Timer Component
typescript
import { BoardAPI } from '@boardapi/sdk';
const board = new BoardAPI();
let timerId: number;
// Initialize
board.on('ready', () => {
const duration = board.props.get('duration', 60);
const autoStart = board.props.get('autoStart', false);
if (autoStart) {
startTimer(duration);
}
});
// Start timer when user interacts
board.on('focus', () => {
const isRunning = board.storage.get('isRunning');
if (isRunning) {
resumeTimer();
}
});
// Pause timer when user leaves
board.on('blur', () => {
pauseTimer();
});
// Cleanup on destroy
board.on('destroy', () => {
clearInterval(timerId);
});
function startTimer(duration: number) {
timerId = setInterval(() => {
board.storage.update('remaining', (prev) => Math.max(0, (prev || duration) - 1));
}, 1000);
board.storage.set('isRunning', true);
}
function pauseTimer() {
clearInterval(timerId);
board.storage.set('isRunning', false);
}
function resumeTimer() {
const remaining = board.storage.get('remaining');
startTimer(remaining);
}Video Player Component
typescript
const board = new BoardAPI();
const video = document.querySelector<HTMLVideoElement>('#video');
// Auto-play on focus
board.on('focus', async () => {
try {
await video.play();
} catch (err) {
console.error('Autoplay prevented:', err);
}
});
// Pause on blur
board.on('blur', () => {
video.pause();
});
// Pause when scrolled out of view
board.on('visibilityChange', (isVisible) => {
if (!isVisible) {
video.pause();
}
});
// Cleanup
board.on('destroy', () => {
video.pause();
video.src = '';
});Canvas Animation
typescript
const board = new BoardAPI();
const canvas = document.querySelector<HTMLCanvasElement>('#canvas');
const ctx = canvas.getContext('2d');
let animationFrame: number;
function animate() {
// Clear and redraw
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawFrame();
animationFrame = requestAnimationFrame(animate);
}
// Start animation on ready (if visible)
board.on('ready', () => {
if (board.lifecycle.isVisible) {
animate();
}
});
// Resume on focus
board.on('focus', () => {
if (!animationFrame) {
animate();
}
});
// Pause on blur
board.on('blur', () => {
if (animationFrame) {
cancelAnimationFrame(animationFrame);
animationFrame = null;
}
});
// Pause when scrolled out
board.on('visibilityChange', (isVisible) => {
if (!isVisible && animationFrame) {
cancelAnimationFrame(animationFrame);
animationFrame = null;
} else if (isVisible && !animationFrame) {
animate();
}
});
// Cleanup
board.on('destroy', () => {
if (animationFrame) {
cancelAnimationFrame(animationFrame);
}
});Auto-Save Form
typescript
const board = new BoardAPI();
let saveTimeout: number;
// Auto-save on input (debounced)
document.querySelector('#form').addEventListener('input', (e) => {
clearTimeout(saveTimeout);
saveTimeout = setTimeout(async () => {
const formData = collectFormData();
await board.storage.set('formData', formData);
board.ui.showToast('Saved', { type: 'success', duration: 1000 });
}, 1000); // Save 1 second after typing stops
});
// Save immediately on blur
board.on('blur', async () => {
clearTimeout(saveTimeout);
const formData = collectFormData();
await board.storage.set('formData', formData);
});
// Cleanup
board.on('destroy', () => {
clearTimeout(saveTimeout);
});Reconnection Handler
typescript
const board = new BoardAPI();
// Show offline indicator
board.connection.on('statusChange', (status) => {
const indicator = document.querySelector('#connection-status');
if (status === 'offline') {
indicator.textContent = 'Offline - Changes will sync when reconnected';
indicator.className = 'offline';
} else if (status === 'connecting') {
indicator.textContent = 'Reconnecting...';
indicator.className = 'connecting';
} else {
indicator.textContent = 'Connected';
indicator.className = 'online';
}
});
// Sync on reconnect
board.on('reconnect', async () => {
console.log('Reconnected - syncing state');
// SDK auto-syncs, but you can also refetch data
await refetchExternalData();
board.ui.showToast('Synced', { type: 'success' });
});Event Order
Typical lifecycle:
1. ready → Component loaded
2. focus → User clicked component
(user interacts)
3. blur → User pressed ESC / clicked outside
4. beforeFreeze → About to generate preview
(component frozen)
... later ...
5. focus → User clicked again
(repeat)
... when removed ...
6. destroy → Component deletedWith network issues:
1. ready
2. (connection lost)
3. reconnect → Connection restoredWith scrolling:
1. ready
2. visibilityChange(false) → Scrolled out
3. visibilityChange(true) → Scrolled backBest Practices
- Always cleanup - Use
destroyevent to prevent memory leaks - Pause on blur - Save battery, prevent background noise
- Use visibility events - Optimize performance when offscreen
- Handle reconnection - Show status, don't lose data
- Debounce auto-save - Don't spam the server
What's Next?
- Storage API - Manage component state
- UI Controls - Update preview, show toasts
- Connection Status - Handle offline mode