Files
michaelschiemer/docs/livecomponents/events-reference.md
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

1355 lines
35 KiB
Markdown

# LiveComponents Events Reference
Complete reference for all client-side events and lifecycle hooks with examples and best practices.
## Table of Contents
- [Lifecycle Events](#lifecycle-events)
- [livecomponent:init](#livecomponentinit)
- [livecomponent:mounted](#livecomponentmounted)
- [livecomponent:updated](#livecomponentupdated)
- [livecomponent:unmounted](#livecomponentunmounted)
- [Action Events](#action-events)
- [livecomponent:action-start](#livecomponentaction-start)
- [livecomponent:action-executed](#livecomponentaction-executed)
- [livecomponent:action-failed](#livecomponentaction-failed)
- [State Events](#state-events)
- [livecomponent:state-changed](#livecomponentstate-changed)
- [livecomponent:prop-updated](#livecomponentprop-updated)
- [Network Events](#network-events)
- [livecomponent:request-start](#livecomponentrequest-start)
- [livecomponent:request-complete](#livecomponentrequest-complete)
- [livecomponent:request-failed](#livecomponentrequest-failed)
- [livecomponent:batch-dispatched](#livecomponentbatch-dispatched)
- [Rendering Events](#rendering-events)
- [livecomponent:render-start](#livecomponentrender-start)
- [livecomponent:render-complete](#livecomponentrender-complete)
- [livecomponent:fragment-updated](#livecomponentfragment-updated)
- [SSE Events](#sse-events)
- [livecomponent:sse-connected](#livecomponentsse-connected)
- [livecomponent:sse-disconnected](#livecomponentsse-disconnected)
- [livecomponent:sse-message](#livecomponentsse-message)
- [livecomponent:sse-error](#livecomponentsse-error)
- [Upload Events](#upload-events)
- [livecomponent:upload-start](#livecomponentupload-start)
- [livecomponent:upload-progress](#livecomponentupload-progress)
- [livecomponent:upload-complete](#livecomponentupload-complete)
- [livecomponent:upload-failed](#livecomponentupload-failed)
- [Error Events](#error-events)
- [livecomponent:error](#livecomponenterror)
- [livecomponent:validation-error](#livecomponentvalidation-error)
- [livecomponent:rate-limit-exceeded](#livecomponentrate-limit-exceeded)
---
## Lifecycle Events
### livecomponent:init
**Fired**: When a LiveComponent is discovered in the DOM but before initialization.
**Use Case**: Prepare component before mounting, modify initial state.
**Event Detail**:
```typescript
{
componentId: string; // Unique component ID
element: HTMLElement; // Component root element
props: object; // Initial component props
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:init', (event) => {
const { componentId, element, props } = event.detail;
console.log('Initializing component:', componentId);
// Modify initial props
if (props.theme === undefined) {
props.theme = localStorage.getItem('theme') || 'light';
}
// Add custom data attributes
element.dataset.initialized = Date.now();
});
```
**Common Use Cases**:
- Restore state from localStorage
- Set default props from user preferences
- Initialize third-party libraries
- Track component initialization analytics
---
### livecomponent:mounted
**Fired**: After a LiveComponent is fully mounted and ready for interaction.
**Use Case**: Initialize JavaScript behaviors, start timers, load external data.
**Event Detail**:
```typescript
{
componentId: string; // Unique component ID
element: HTMLElement; // Component root element
component: Component; // Component instance
props: object; // Current component props
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:mounted', (event) => {
const { componentId, element, component } = event.detail;
// Initialize tooltips
const tooltips = element.querySelectorAll('[data-tooltip]');
tooltips.forEach(el => new Tooltip(el));
// Start auto-refresh timer
if (component.config.autoRefresh) {
setInterval(() => {
component.executeAction('refresh');
}, component.config.refreshInterval || 30000);
}
// Track mount analytics
analytics.track('component_mounted', {
component_type: componentId,
mount_time: Date.now()
});
});
```
**Common Use Cases**:
- Initialize third-party plugins (charts, maps, editors)
- Start polling/auto-refresh timers
- Bind custom event listeners
- Load deferred content
- Track analytics
---
### livecomponent:updated
**Fired**: After a LiveComponent has been updated (re-rendered).
**Use Case**: React to state changes, update third-party integrations, track updates.
**Event Detail**:
```typescript
{
componentId: string; // Unique component ID
element: HTMLElement; // Component root element
component: Component; // Component instance
previousProps: object; // Props before update
currentProps: object; // Props after update
changedProps: string[]; // Names of changed props
fragments: string[]; // Updated fragments (if partial render)
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:updated', (event) => {
const { componentId, previousProps, currentProps, changedProps, fragments } = event.detail;
// Re-initialize elements in updated fragments
if (fragments.includes('chart-container')) {
const chartEl = document.querySelector('[data-lc-fragment="chart-container"]');
initializeChart(chartEl, currentProps.chartData);
}
// Track specific prop changes
if (changedProps.includes('status')) {
console.log('Status changed:', previousProps.status, '→', currentProps.status);
if (currentProps.status === 'completed') {
showSuccessNotification();
}
}
// Update document title
if (changedProps.includes('unreadCount')) {
document.title = currentProps.unreadCount > 0
? `(${currentProps.unreadCount}) Messages`
: 'Messages';
}
});
```
**Common Use Cases**:
- Re-initialize third-party components in updated fragments
- Trigger animations on state changes
- Update browser title/favicon based on state
- Track state change analytics
- Sync with external systems
---
### livecomponent:unmounted
**Fired**: Before a LiveComponent is removed from the DOM.
**Use Case**: Cleanup timers, remove event listeners, save state.
**Event Detail**:
```typescript
{
componentId: string; // Unique component ID
element: HTMLElement; // Component root element (about to be removed)
component: Component; // Component instance
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:unmounted', (event) => {
const { componentId, element, component } = event.detail;
// Save state to localStorage
localStorage.setItem(`component-${componentId}`, JSON.stringify(component.props));
// Clear timers
if (window.componentTimers[componentId]) {
clearInterval(window.componentTimers[componentId]);
delete window.componentTimers[componentId];
}
// Destroy third-party instances
if (window.chartInstances[componentId]) {
window.chartInstances[componentId].destroy();
delete window.chartInstances[componentId];
}
// Track unmount analytics
analytics.track('component_unmounted', {
component_type: componentId,
lifetime_ms: Date.now() - parseInt(element.dataset.initialized)
});
});
```
**Common Use Cases**:
- Save component state before removal
- Clear timers and intervals
- Remove global event listeners
- Destroy third-party plugin instances
- Cancel pending network requests
- Track component lifetime analytics
---
## Action Events
### livecomponent:action-start
**Fired**: When a LiveAction is triggered (before server request).
**Use Case**: Show loading states, disable buttons, track action starts.
**Event Detail**:
```typescript
{
componentId: string; // Component ID
action: string; // Action name
params: object; // Action parameters
element: HTMLElement; // Element that triggered action
timestamp: number; // Start timestamp
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:action-start', (event) => {
const { componentId, action, params, element } = event.detail;
// Show loading state
element.classList.add('loading');
element.disabled = true;
// Show loading spinner for long-running actions
if (['generateReport', 'processPayment'].includes(action)) {
showLoadingSpinner(`Processing ${action}...`);
}
// Track action starts
analytics.track('action_started', {
component: componentId,
action: action,
params: params
});
});
```
**Common Use Cases**:
- Show loading indicators
- Disable form buttons to prevent double-submission
- Show progress overlays
- Track action analytics
- Optimistic UI updates
---
### livecomponent:action-executed
**Fired**: After a LiveAction completes successfully.
**Use Case**: Hide loading states, show success messages, track performance.
**Event Detail**:
```typescript
{
componentId: string; // Component ID
action: string; // Action name
params: object; // Action parameters
result: any; // Action result
duration: number; // Execution time (ms)
timestamp: number; // Completion timestamp
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:action-executed', (event) => {
const { componentId, action, result, duration } = event.detail;
// Hide loading states
hideLoadingSpinner();
// Show success message for specific actions
if (action === 'saveForm') {
showToast('Changes saved successfully!', 'success');
}
// Track slow actions
if (duration > 1000) {
console.warn(`Slow action: ${action} took ${duration}ms`);
analytics.track('slow_action', {
component: componentId,
action: action,
duration: duration
});
}
// Update badge counts
if (action === 'markAsRead') {
const badge = document.querySelector('.unread-badge');
const count = parseInt(badge.textContent) - 1;
badge.textContent = count > 0 ? count : '';
}
});
```
**Common Use Cases**:
- Hide loading indicators
- Show success notifications
- Update UI elements outside component
- Track action performance
- Trigger follow-up actions
---
### livecomponent:action-failed
**Fired**: When a LiveAction fails (validation, authorization, or server error).
**Use Case**: Show error messages, re-enable forms, track failures.
**Event Detail**:
```typescript
{
componentId: string; // Component ID
action: string; // Action name
params: object; // Action parameters
error: {
message: string; // Error message
code: string; // Error code
details: object; // Additional error details
};
duration: number; // Time before failure (ms)
timestamp: number; // Failure timestamp
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:action-failed', (event) => {
const { componentId, action, error, element } = event.detail;
// Re-enable form elements
if (element) {
element.classList.remove('loading');
element.disabled = false;
}
// Show error message
if (error.code === 'VALIDATION_ERROR') {
showValidationErrors(error.details);
} else if (error.code === 'RATE_LIMIT_EXCEEDED') {
showToast(`Too many requests. Please wait ${error.details.retryAfter} seconds.`, 'warning');
} else {
showToast(error.message || 'An error occurred', 'error');
}
// Track failures
analytics.track('action_failed', {
component: componentId,
action: action,
error_code: error.code,
error_message: error.message
});
});
```
**Common Use Cases**:
- Display error messages
- Re-enable disabled form elements
- Show validation errors
- Track error rates
- Trigger error recovery flows
---
## State Events
### livecomponent:state-changed
**Fired**: When component state changes (any LiveProp update).
**Use Case**: React to any state change, sync with external systems.
**Event Detail**:
```typescript
{
componentId: string; // Component ID
previousState: object; // State before change
currentState: object; // State after change
changedProps: string[]; // Names of changed props
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:state-changed', (event) => {
const { componentId, previousState, currentState, changedProps } = event.detail;
// Sync with localStorage
localStorage.setItem(`state-${componentId}`, JSON.stringify(currentState));
// Track state changes
changedProps.forEach(prop => {
console.log(`${prop} changed:`, previousState[prop], '→', currentState[prop]);
});
// Trigger side effects
if (changedProps.includes('selectedItems')) {
updateBulkActionBar(currentState.selectedItems);
}
});
```
**Common Use Cases**:
- Persist state to localStorage
- Sync with external state management
- Update derived UI elements
- Track state change analytics
- Debug state changes
---
### livecomponent:prop-updated
**Fired**: When a specific LiveProp is updated.
**Use Case**: React to specific property changes, trigger dependent updates.
**Event Detail**:
```typescript
{
componentId: string; // Component ID
propName: string; // Updated property name
previousValue: any; // Value before update
currentValue: any; // Value after update
source: 'server' | 'client' | 'optimistic'; // Update source
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:prop-updated', (event) => {
const { componentId, propName, previousValue, currentValue, source } = event.detail;
// React to specific prop changes
if (propName === 'theme') {
document.body.classList.remove(`theme-${previousValue}`);
document.body.classList.add(`theme-${currentValue}`);
localStorage.setItem('theme', currentValue);
}
if (propName === 'count' && source === 'optimistic') {
// Animate optimistic updates
animateNumberChange(previousValue, currentValue);
}
if (propName === 'sortOrder') {
// Re-sort table when sort order changes
sortTable(currentValue);
}
});
```
**Common Use Cases**:
- Trigger animations on specific prop changes
- Update global UI state (theme, locale)
- Sync specific props with localStorage
- Track important prop changes
- Trigger dependent updates
---
## Network Events
### livecomponent:request-start
**Fired**: When a network request starts (action or state sync).
**Use Case**: Show global loading indicators, track request starts.
**Event Detail**:
```typescript
{
componentId: string; // Component ID
requestId: string; // Unique request ID
type: 'action' | 'sync'; // Request type
payload: object; // Request payload
timestamp: number; // Start timestamp
}
```
**Example**:
```javascript
let activeRequests = 0;
window.addEventListener('livecomponent:request-start', (event) => {
const { componentId, requestId, type } = event.detail;
activeRequests++;
// Show global loading indicator
if (activeRequests === 1) {
document.body.classList.add('loading');
}
// Track request
console.log(`Request ${requestId} started for ${componentId} (type: ${type})`);
});
```
---
### livecomponent:request-complete
**Fired**: When a network request completes successfully.
**Use Case**: Hide loading indicators, track request performance.
**Event Detail**:
```typescript
{
componentId: string; // Component ID
requestId: string; // Unique request ID
type: 'action' | 'sync'; // Request type
response: object; // Server response
duration: number; // Request duration (ms)
timestamp: number; // Completion timestamp
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:request-complete', (event) => {
const { componentId, requestId, duration } = event.detail;
activeRequests--;
// Hide global loading indicator
if (activeRequests === 0) {
document.body.classList.remove('loading');
}
// Track slow requests
if (duration > 1000) {
console.warn(`Slow request: ${requestId} took ${duration}ms`);
}
});
```
---
### livecomponent:request-failed
**Fired**: When a network request fails.
**Use Case**: Handle network errors, retry logic, track failures.
**Event Detail**:
```typescript
{
componentId: string; // Component ID
requestId: string; // Unique request ID
type: 'action' | 'sync'; // Request type
error: {
message: string; // Error message
code: string; // Error code
status: number; // HTTP status code
};
duration: number; // Time before failure (ms)
timestamp: number; // Failure timestamp
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:request-failed', (event) => {
const { componentId, requestId, error, duration } = event.detail;
activeRequests--;
// Hide global loading indicator
if (activeRequests === 0) {
document.body.classList.remove('loading');
}
// Handle network errors
if (error.status === 0) {
showToast('Network error. Please check your connection.', 'error');
} else if (error.status === 429) {
showToast('Too many requests. Please slow down.', 'warning');
} else if (error.status >= 500) {
showToast('Server error. Please try again later.', 'error');
}
// Track failures
analytics.track('request_failed', {
component: componentId,
request_id: requestId,
error_code: error.code,
status: error.status
});
});
```
---
### livecomponent:batch-dispatched
**Fired**: When batched requests are sent to server.
**Use Case**: Track batching efficiency, debug batch behavior.
**Event Detail**:
```typescript
{
batchId: string; // Unique batch ID
actions: Array<{ // Batched actions
componentId: string;
action: string;
params: object;
}>;
batchSize: number; // Number of actions in batch
timestamp: number; // Dispatch timestamp
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:batch-dispatched', (event) => {
const { batchId, actions, batchSize } = event.detail;
console.log(`Batch ${batchId} dispatched with ${batchSize} actions:`, actions);
// Track batching efficiency
analytics.track('batch_dispatched', {
batch_id: batchId,
batch_size: batchSize,
components: [...new Set(actions.map(a => a.componentId))]
});
});
```
---
## Rendering Events
### livecomponent:render-start
**Fired**: Before component re-rendering begins.
**Use Case**: Save scroll position, prepare for render.
**Event Detail**:
```typescript
{
componentId: string; // Component ID
fragments: string[]; // Fragments to be rendered
timestamp: number; // Render start timestamp
}
```
**Example**:
```javascript
const scrollPositions = new Map();
window.addEventListener('livecomponent:render-start', (event) => {
const { componentId, fragments } = event.detail;
// Save scroll positions of fragments
fragments.forEach(fragmentName => {
const fragment = document.querySelector(`[data-lc-fragment="${fragmentName}"]`);
if (fragment) {
scrollPositions.set(fragmentName, {
scrollTop: fragment.scrollTop,
scrollLeft: fragment.scrollLeft
});
}
});
});
```
---
### livecomponent:render-complete
**Fired**: After component re-rendering completes.
**Use Case**: Restore scroll position, re-initialize JavaScript.
**Event Detail**:
```typescript
{
componentId: string; // Component ID
fragments: string[]; // Rendered fragments
duration: number; // Render duration (ms)
timestamp: number; // Render complete timestamp
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:render-complete', (event) => {
const { componentId, fragments, duration } = event.detail;
// Restore scroll positions
fragments.forEach(fragmentName => {
const fragment = document.querySelector(`[data-lc-fragment="${fragmentName}"]`);
const savedPosition = scrollPositions.get(fragmentName);
if (fragment && savedPosition) {
fragment.scrollTop = savedPosition.scrollTop;
fragment.scrollLeft = savedPosition.scrollLeft;
}
});
// Re-initialize JavaScript in rendered fragments
fragments.forEach(fragmentName => {
const fragment = document.querySelector(`[data-lc-fragment="${fragmentName}"]`);
initializeFragment(fragment);
});
// Track slow renders
if (duration > 100) {
console.warn(`Slow render: ${componentId} took ${duration}ms`);
}
});
```
---
### livecomponent:fragment-updated
**Fired**: When a specific fragment is updated.
**Use Case**: Re-initialize fragment-specific JavaScript, track updates.
**Event Detail**:
```typescript
{
componentId: string; // Component ID
fragmentName: string; // Updated fragment name
element: HTMLElement; // Fragment element
timestamp: number; // Update timestamp
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:fragment-updated', (event) => {
const { componentId, fragmentName, element } = event.detail;
// Re-initialize specific fragment behaviors
if (fragmentName === 'chart-container') {
const chartEl = element.querySelector('.chart');
if (chartEl) {
initializeChart(chartEl);
}
}
if (fragmentName === 'image-gallery') {
const images = element.querySelectorAll('img[data-lazy]');
lazyLoadImages(images);
}
// Track fragment updates
analytics.track('fragment_updated', {
component: componentId,
fragment: fragmentName
});
});
```
---
## SSE Events
### livecomponent:sse-connected
**Fired**: When SSE connection is established.
**Use Case**: Show online status, enable real-time features.
**Event Detail**:
```typescript
{
componentId: string; // Component ID
url: string; // SSE endpoint URL
timestamp: number; // Connection timestamp
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:sse-connected', (event) => {
const { componentId, url } = event.detail;
console.log(`SSE connected for ${componentId} at ${url}`);
// Show online indicator
const indicator = document.querySelector('.connection-status');
indicator.classList.remove('offline');
indicator.classList.add('online');
indicator.textContent = 'Connected';
});
```
---
### livecomponent:sse-disconnected
**Fired**: When SSE connection is lost.
**Use Case**: Show offline status, disable real-time features.
**Event Detail**:
```typescript
{
componentId: string; // Component ID
reason: string; // Disconnection reason
willReconnect: boolean; // Auto-reconnect enabled
timestamp: number; // Disconnection timestamp
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:sse-disconnected', (event) => {
const { componentId, reason, willReconnect } = event.detail;
console.warn(`SSE disconnected for ${componentId}: ${reason}`);
// Show offline indicator
const indicator = document.querySelector('.connection-status');
indicator.classList.remove('online');
indicator.classList.add('offline');
indicator.textContent = willReconnect ? 'Reconnecting...' : 'Offline';
});
```
---
### livecomponent:sse-message
**Fired**: When an SSE message is received.
**Use Case**: Handle real-time updates, show notifications.
**Event Detail**:
```typescript
{
componentId: string; // Component ID
event: string; // SSE event type
data: any; // Message data
timestamp: number; // Message timestamp
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:sse-message', (event) => {
const { componentId, event: eventType, data } = event.detail;
if (eventType === 'notification') {
showNotification(data.title, data.body);
}
if (eventType === 'user-joined') {
updateOnlineUsers(data.userId, data.userName);
}
if (eventType === 'price-update') {
animatePriceChange(data.symbol, data.price);
}
});
```
---
### livecomponent:sse-error
**Fired**: When an SSE error occurs.
**Use Case**: Handle SSE errors, implement fallback.
**Event Detail**:
```typescript
{
componentId: string; // Component ID
error: {
message: string; // Error message
code: string; // Error code
};
timestamp: number; // Error timestamp
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:sse-error', (event) => {
const { componentId, error } = event.detail;
console.error(`SSE error for ${componentId}:`, error.message);
// Fallback to polling
if (!window.pollingTimers[componentId]) {
window.pollingTimers[componentId] = setInterval(() => {
LiveComponent.getComponent(componentId).executeAction('refresh');
}, 5000);
}
});
```
---
## Upload Events
### livecomponent:upload-start
**Fired**: When file upload starts.
**Use Case**: Show upload progress UI.
**Event Detail**:
```typescript
{
componentId: string; // Component ID
uploadId: string; // Unique upload ID
file: {
name: string;
size: number;
type: string;
};
chunks: number; // Total number of chunks
timestamp: number; // Upload start timestamp
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:upload-start', (event) => {
const { uploadId, file, chunks } = event.detail;
// Create progress UI
const progressEl = document.createElement('div');
progressEl.id = `upload-${uploadId}`;
progressEl.innerHTML = `
<div class="upload-item">
<span class="filename">${file.name}</span>
<div class="progress-bar">
<div class="progress" style="width: 0%"></div>
</div>
<span class="status">Uploading (0/${chunks} chunks)...</span>
</div>
`;
document.querySelector('.upload-queue').appendChild(progressEl);
});
```
---
### livecomponent:upload-progress
**Fired**: During file upload (per chunk).
**Use Case**: Update progress bar, show upload speed.
**Event Detail**:
```typescript
{
componentId: string; // Component ID
uploadId: string; // Upload ID
progress: {
loaded: number; // Bytes uploaded
total: number; // Total bytes
percentage: number; // Progress percentage
chunk: number; // Current chunk
totalChunks: number; // Total chunks
};
timestamp: number; // Progress update timestamp
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:upload-progress', (event) => {
const { uploadId, progress } = event.detail;
// Update progress bar
const progressEl = document.querySelector(`#upload-${uploadId}`);
const progressBar = progressEl.querySelector('.progress');
const statusEl = progressEl.querySelector('.status');
progressBar.style.width = `${progress.percentage}%`;
statusEl.textContent = `Uploading (${progress.chunk}/${progress.totalChunks} chunks) - ${progress.percentage}%`;
});
```
---
### livecomponent:upload-complete
**Fired**: When file upload completes successfully.
**Use Case**: Show success message, clean up progress UI.
**Event Detail**:
```typescript
{
componentId: string; // Component ID
uploadId: string; // Upload ID
file: {
name: string;
size: number;
url: string; // Uploaded file URL
};
duration: number; // Upload duration (ms)
timestamp: number; // Completion timestamp
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:upload-complete', (event) => {
const { uploadId, file, duration } = event.detail;
// Update progress UI
const progressEl = document.querySelector(`#upload-${uploadId}`);
const statusEl = progressEl.querySelector('.status');
statusEl.textContent = `✓ Complete (${(duration / 1000).toFixed(1)}s)`;
statusEl.classList.add('success');
// Remove after 3 seconds
setTimeout(() => progressEl.remove(), 3000);
// Show success toast
showToast(`${file.name} uploaded successfully!`, 'success');
});
```
---
### livecomponent:upload-failed
**Fired**: When file upload fails.
**Use Case**: Show error message, allow retry.
**Event Detail**:
```typescript
{
componentId: string; // Component ID
uploadId: string; // Upload ID
file: {
name: string;
size: number;
};
error: {
message: string; // Error message
code: string; // Error code
chunk: number; // Failed chunk (if applicable)
};
timestamp: number; // Failure timestamp
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:upload-failed', (event) => {
const { uploadId, file, error } = event.detail;
// Update progress UI
const progressEl = document.querySelector(`#upload-${uploadId}`);
const statusEl = progressEl.querySelector('.status');
statusEl.textContent = `✗ Failed: ${error.message}`;
statusEl.classList.add('error');
// Add retry button
const retryBtn = document.createElement('button');
retryBtn.textContent = 'Retry';
retryBtn.onclick = () => retryUpload(uploadId);
progressEl.appendChild(retryBtn);
// Show error toast
showToast(`Failed to upload ${file.name}: ${error.message}`, 'error');
});
```
---
## Error Events
### livecomponent:error
**Fired**: When a general LiveComponent error occurs.
**Use Case**: Global error handling, error tracking.
**Event Detail**:
```typescript
{
componentId: string; // Component ID
error: {
message: string; // Error message
code: string; // Error code
stack: string; // Stack trace
};
context: string; // Error context ('render', 'action', 'network', etc.)
timestamp: number; // Error timestamp
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:error', (event) => {
const { componentId, error, context } = event.detail;
console.error(`LiveComponent error in ${componentId} (${context}):`, error);
// Send to error tracking service
if (window.errorTracker) {
window.errorTracker.captureException(error, {
component: componentId,
context: context
});
}
// Show generic error message to user
showToast('An unexpected error occurred. Please refresh the page.', 'error');
});
```
---
### livecomponent:validation-error
**Fired**: When server-side validation fails.
**Use Case**: Display validation errors, focus first error.
**Event Detail**:
```typescript
{
componentId: string; // Component ID
action: string; // Action that failed validation
errors: Record<string, string[]>; // Validation errors by field
timestamp: number; // Error timestamp
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:validation-error', (event) => {
const { componentId, action, errors } = event.detail;
// Display errors next to fields
Object.entries(errors).forEach(([field, messages]) => {
const input = document.querySelector(`[name="${field}"]`);
const errorEl = input.parentElement.querySelector('.error-message');
if (errorEl) {
errorEl.textContent = messages[0];
errorEl.style.display = 'block';
}
input.classList.add('invalid');
});
// Focus first invalid field
const firstInvalid = document.querySelector('.invalid');
if (firstInvalid) {
firstInvalid.focus();
}
});
```
---
### livecomponent:rate-limit-exceeded
**Fired**: When rate limit is exceeded for an action.
**Use Case**: Show rate limit message, display countdown.
**Event Detail**:
```typescript
{
componentId: string; // Component ID
action: string; // Rate-limited action
retryAfter: number; // Seconds until retry allowed
timestamp: number; // Rate limit timestamp
}
```
**Example**:
```javascript
window.addEventListener('livecomponent:rate-limit-exceeded', (event) => {
const { componentId, action, retryAfter } = event.detail;
// Show countdown toast
let remaining = retryAfter;
const toastId = showToast(
`Rate limit exceeded. Please wait ${remaining} seconds.`,
'warning',
{ duration: retryAfter * 1000 }
);
const countdown = setInterval(() => {
remaining--;
if (remaining > 0) {
updateToast(toastId, `Rate limit exceeded. Please wait ${remaining} seconds.`);
} else {
clearInterval(countdown);
updateToast(toastId, 'You can try again now!');
}
}, 1000);
});
```
---
## Event Patterns
### Event Delegation
```javascript
// Instead of listening on each component
document.addEventListener('livecomponent:action-executed', (event) => {
if (event.detail.componentId.startsWith('notification-')) {
updateNotificationBadge();
}
});
```
### Debounced Event Handlers
```javascript
import { debounce } from './utils';
window.addEventListener('livecomponent:prop-updated',
debounce((event) => {
if (event.detail.propName === 'searchQuery') {
updateSearchResults(event.detail.currentValue);
}
}, 300)
);
```
### Global Loading Indicator
```javascript
let activeRequests = 0;
window.addEventListener('livecomponent:request-start', () => {
activeRequests++;
if (activeRequests === 1) {
document.body.classList.add('loading');
}
});
window.addEventListener('livecomponent:request-complete', () => {
activeRequests--;
if (activeRequests === 0) {
document.body.classList.remove('loading');
}
});
window.addEventListener('livecomponent:request-failed', () => {
activeRequests--;
if (activeRequests === 0) {
document.body.classList.remove('loading');
}
});
```
### Performance Monitoring
```javascript
window.addEventListener('livecomponent:action-executed', (event) => {
const { action, duration } = event.detail;
if (duration > 1000) {
console.warn(`Slow action: ${action} (${duration}ms)`);
}
// Track to analytics
analytics.track('action_performance', {
action: action,
duration: duration
});
});
```
---
## Best Practices
1. **Use Event Delegation**: Listen on `window` instead of individual elements
2. **Cleanup Listeners**: Remove listeners in `unmounted` event
3. **Debounce/Throttle**: Use debouncing for high-frequency events
4. **Error Handling**: Always handle errors gracefully
5. **Performance**: Minimize work in event handlers
6. **Analytics**: Track important events for monitoring
7. **User Feedback**: Provide visual feedback for all events
8. **Accessibility**: Announce important events to screen readers
---
For more examples and patterns, see:
- [Advanced Features Guide](advanced-features.md)
- [Troubleshooting Guide](troubleshooting.md)
- [API Reference](api-reference.md)