UNPKG

invokers

Version:

A powerful, platform-first JavaScript library for creating modern user interfaces with declarative HTML. Features universal command chaining, conditional execution, and declarative workflow orchestration via `data-and-then` attributes and `<and-then>` ele

400 lines (344 loc) 15.5 kB
/** * @file demo-commands.ts * @summary Demo commands for the Invokers comprehensive demo. * @description * This module contains demo-specific commands used only for testing and demonstration * purposes in the comprehensive demo. These commands are not included in the main * Invokers library and should be imported separately when needed. * * To use these commands, import and run the `registerDemoCommands()` function * after the core Invokers library has been loaded. * @example * // In your demo page: * import 'invokers'; // Core library (loads polyfill and window.Invoker) * import { registerDemoCommands } from './demo-commands'; * * // Make demo commands available for testing * registerDemoCommands(); */ import type { CommandContext } from "./index"; import { createInvokerError, ErrorSeverity } from "./index"; type CommandRegistry = Record<string, (context: CommandContext) => void | Promise<void>>; /** * Demo-specific commands for testing and demonstration purposes. * These commands are not part of the core Invokers library. */ export const demoCommands: CommandRegistry = { /** * `--demo:echo`: Demo command for echoing input data. * @example `<input command-on="input" command="--demo:echo" commandfor="display">` */ "--demo:echo": ({ targetElement }: CommandContext) => { // Demo command - in real implementation this would handle the echo logic targetElement.textContent = `Echo: ${new Date().toISOString()}`; }, /** * `--demo:counter:increment`: Demo counter increment command. * @example `<button command="--demo:counter:increment" data-max-count="10">` */ "--demo:counter:increment": ({ invoker, targetElement }: CommandContext) => { const maxCount = parseInt(invoker.dataset.maxCount || '100', 10); let currentCount = parseInt(document.body.dataset.counter || '0', 10); if (currentCount < maxCount) { currentCount++; document.body.dataset.counter = currentCount.toString(); targetElement.textContent = currentCount.toString(); // Dispatch custom event targetElement.dispatchEvent(new CustomEvent('counter-updated', { bubbles: true, detail: { count: currentCount } })); } }, /** * `--demo:counter:reset`: Demo counter reset command. * @example `<button command="--demo:counter:reset">` */ "--demo:counter:reset": ({ targetElement }: CommandContext) => { document.body.dataset.counter = '0'; targetElement.textContent = '0'; // Reset progress bar const progressBar = document.getElementById('progress-fill'); if (progressBar) progressBar.style.width = '0%'; }, /** * `--demo:fetch:api`: Demo API fetch command. * @example `<button command="--demo:fetch:api" data-url="https://api.example.com">` */ "--demo:fetch:api": async ({ invoker, targetElement }: CommandContext) => { const url = invoker.dataset.url || 'https://jsonplaceholder.typicode.com/posts/1'; targetElement.innerHTML = '<div style="text-align: center; padding: 20px;"><div class="status-indicator status-loading pulse"></div>Loading...</div>'; try { const response = await fetch(url); if (!response.ok) throw new Error(`HTTP ${response.status}`); const data = await response.json(); targetElement.innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); targetElement.innerHTML = `<div style="color: #e53e3e; padding: 20px;"><div class="status-indicator status-error"></div>Error: ${errorMessage}</div>`; } }, /** * `--demo:fetch:simulate-error`: Demo error simulation command. * @example `<button command="--demo:fetch:simulate-error">` */ "--demo:fetch:simulate-error": async ({ targetElement }: CommandContext) => { targetElement.innerHTML = '<div style="text-align: center; padding: 20px;"><div class="status-indicator status-loading pulse"></div>Loading...</div>'; // Simulate network delay await new Promise(resolve => setTimeout(resolve, 1000)); targetElement.innerHTML = '<div style="color: #e53e3e; padding: 20px;"><div class="status-indicator status-error"></div>Simulated network error</div>'; }, /** * `--demo:chain:start`: Demo command chaining start. * @example `<button command="--demo:chain:start" data-and-then="--demo:chain:step2">` */ "--demo:chain:start": async ({ targetElement }: CommandContext) => { targetElement.textContent = 'Step 1: Starting chain...\n'; await new Promise(resolve => setTimeout(resolve, 500)); targetElement.textContent += 'Step 1: Completed\n'; }, /** * `--demo:event:publish`: Demo custom event publishing. * @example `<button command="--demo:event:publish" data-event-type="user-action">` */ "--demo:event:publish": ({ invoker, targetElement }: CommandContext) => { const eventType = invoker.dataset.eventType || 'demo-event'; const eventData = invoker.dataset.eventData ? JSON.parse(invoker.dataset.eventData) : {}; targetElement.textContent += `Published event: ${eventType}\n`; targetElement.textContent += `Data: ${JSON.stringify(eventData)}\n`; // Dispatch custom event document.dispatchEvent(new CustomEvent('demo-notification', { detail: { type: 'info', title: 'Event Published', message: `Event "${eventType}" was published successfully` } })); }, /** * `--demo:chart:add-point`: Demo Chart.js integration. * @example `<button command="--demo:chart:add-point" data-value-from="input-id">` */ "--demo:chart:add-point": ({ invoker, targetElement }: CommandContext) => { const valueSource = invoker.dataset.valueFrom; let value = Math.random() * 100; if (valueSource) { const input = document.getElementById(valueSource) as HTMLInputElement; if (input && input.value) { value = parseFloat(input.value) || value; } } // In a real implementation, this would update the Chart.js instance const demoChart = (window as any).demoChart; if (demoChart) { demoChart.data.labels.push(new Date().toLocaleTimeString()); demoChart.data.datasets[0].data.push(value); // Keep only last 10 points if (demoChart.data.labels.length > 10) { demoChart.data.labels.shift(); demoChart.data.datasets[0].data.shift(); } demoChart.update(); } targetElement.textContent = `Added point: ${value.toFixed(2)}`; }, /** * `--demo:chart:clear`: Demo chart clearing command. * @example `<button command="--demo:chart:clear">` */ "--demo:chart:clear": ({ targetElement }: CommandContext) => { const demoChart = (window as any).demoChart; if (demoChart) { demoChart.data.labels = []; demoChart.data.datasets[0].data = []; demoChart.update(); } targetElement.textContent = 'Chart cleared'; }, /** * `--demo:api:github-user`: Demo GitHub API integration. * @example `<button command="--demo:api:github-user" data-username="octocat">` */ "--demo:api:github-user": async ({ invoker, targetElement }: CommandContext) => { const username = invoker.dataset.username || 'octocat'; targetElement.innerHTML = '<div style="text-align: center; padding: 20px;"><div class="status-indicator status-loading pulse"></div>Loading GitHub user...</div>'; try { const response = await fetch(`https://api.github.com/users/${username}`); if (!response.ok) throw new Error(`GitHub API error: ${response.status}`); const user = await response.json(); targetElement.innerHTML = ` <div style="text-align: center;"> <img src="${user.avatar_url}" alt="${user.login}" style="width: 100px; height: 100px; border-radius: 50%;"> <h3>${user.name || user.login}</h3> <p>${user.bio || 'No bio available'}</p> <p>Followers: ${user.followers} | Following: ${user.following}</p> <p>Public repos: ${user.public_repos}</p> </div> `; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); targetElement.innerHTML = `<div style="color: #e53e3e; padding: 20px;"><div class="status-indicator status-error"></div>Error loading GitHub user: ${errorMessage}</div>`; } }, /** * `--demo:template:load-list`: Demo template rendering. * @example `<button command="--demo:template:load-list">` */ "--demo:template:load-list": ({ targetElement }: CommandContext) => { const sampleData = [ { id: 1, title: 'Learn Invokers', completed: true, priority: 'high' }, { id: 2, title: 'Build Amazing UI', completed: false, priority: 'medium' }, { id: 3, title: 'Master JavaScript', completed: false, priority: 'low' } ]; let html = '<div style="max-height: 300px; overflow-y: auto;">'; sampleData.forEach(item => { const completedClass = item.completed ? 'completed' : ''; html += ` <div class="todo-item ${completedClass}" style="padding: 10px; border-bottom: 1px solid #eee;"> <strong>${item.title}</strong> <span style="float: right; color: ${item.priority === 'high' ? '#e53e3e' : item.priority === 'medium' ? '#d69e2e' : '#38a169'};">${item.priority}</span> ${item.completed ? '<span style="color: #38a169;">✓</span>' : ''} </div> `; }); html += '</div>'; targetElement.innerHTML = html; }, /** * `--demo:condition:update`: Demo conditional rendering. * @example `<select command="--demo:condition:update">` */ "--demo:condition:update": ({ invoker, targetElement }: CommandContext) => { const condition = (invoker as unknown as HTMLSelectElement).value; let content = ''; switch (condition) { case 'loading': content = '<div style="text-align: center; padding: 20px;"><div class="status-indicator status-loading pulse"></div>Loading content...</div>'; break; case 'success': content = '<div style="text-align: center; padding: 20px; color: #38a169;"><div class="status-indicator status-success"></div>Content loaded successfully!</div>'; break; case 'error': content = '<div style="text-align: center; padding: 20px; color: #e53e3e;"><div class="status-indicator status-error"></div>Error loading content</div>'; break; } targetElement.innerHTML = content; }, /** * `--demo:programmatic`: Demo programmatic command execution. * @example `window.Invoker.executeCommand('--demo:programmatic', 'target')` */ "--demo:programmatic": ({ targetElement }: CommandContext) => { targetElement.textContent += `Programmatic execution at ${new Date().toLocaleTimeString()}\n`; }, /** * `--demo:delayed`: Demo delayed command execution. * @example `setTimeout(() => window.Invoker.executeCommand('--demo:delayed'), 2000)` */ "--demo:delayed": ({ targetElement }: CommandContext) => { targetElement.textContent += `Delayed execution completed at ${new Date().toLocaleTimeString()}\n`; }, /** * `--demo:queue:start`: Demo command queuing. * @example `<button command="--demo:queue:start">` */ "--demo:queue:start": async ({ targetElement }: CommandContext) => { targetElement.textContent = 'Starting command queue...\n'; const steps = ['Initializing...', 'Processing data...', 'Validating...', 'Completing...']; for (const step of steps) { targetElement.textContent += `${step}\n`; await new Promise(resolve => setTimeout(resolve, 300)); } targetElement.textContent += 'Queue execution completed!\n'; }, /** * `--demo:error:network`: Demo network error handling. * @example `<button command="--demo:error:network">` */ "--demo:error:network": (_: CommandContext) => { throw createInvokerError('Network connection failed', ErrorSeverity.ERROR, { command: '--demo:error:network', recovery: 'Check your internet connection and try again.' }); }, /** * `--demo:error:validation`: Demo validation error handling. * @example `<button command="--demo:error:validation">` */ "--demo:error:validation": (_: CommandContext) => { throw createInvokerError('Invalid input data provided', ErrorSeverity.ERROR, { command: '--demo:error:validation', recovery: 'Please check your input and ensure all required fields are filled.' }); }, /** * `--demo:error:timeout`: Demo timeout error handling. * @example `<button command="--demo:error:timeout">` */ "--demo:error:timeout": (_: CommandContext) => { throw createInvokerError('Operation timed out', ErrorSeverity.ERROR, { command: '--demo:error:timeout', recovery: 'The operation took too long to complete. Please try again.' }); }, /** * `--demo:debug:toggle`: Demo debug mode toggling. * @example `<button command="--demo:debug:toggle">` */ "--demo:debug:toggle": ({ targetElement }: CommandContext) => { const currentMode = document.body.dataset.debugMode === 'true'; const newMode = !currentMode; document.body.dataset.debugMode = newMode.toString(); targetElement.textContent = `Debug mode: ${newMode ? 'ENABLED' : 'DISABLED'}\n`; targetElement.textContent += newMode ? 'Detailed command execution logs will be shown in console.\n' : 'Debug logging disabled.\n'; }, /** * `--demo:debug:execute`: Demo command execution with debug info. * @example `<button command="--demo:debug:execute">` */ "--demo:debug:execute": ({ targetElement }: CommandContext) => { const timestamp = new Date().toISOString(); const debugInfo = { command: '--demo:debug:execute', timestamp, userAgent: navigator.userAgent, url: window.location.href, debugMode: document.body.dataset.debugMode === 'true' }; targetElement.innerHTML = `<pre>${JSON.stringify(debugInfo, null, 2)}</pre>`; }, /** * `--demo:clear`: Demo content clearing command. * @example `<button command="--demo:clear" commandfor="target">` */ "--demo:clear": ({ targetElement }: CommandContext) => { targetElement.textContent = ''; }, }; /** * Registers demo commands with the global `Invoker` instance. * These commands are only for testing and demonstration purposes. * * @param specificCommands An optional array of command names to register. If omitted, all demo commands are registered. * @example * registerDemoCommands(); // Registers all demo commands * registerDemoCommands(['--demo:echo', '--demo:counter:increment']); // Registers specific demo commands */ export function registerDemoCommands(specificCommands?: string[]): void { if (!window.Invoker?.register) { console.error("Invokers: Core library not found. Ensure it is loaded before the demo commands module."); return; } const commandsToRegister = specificCommands || Object.keys(demoCommands); for (const name of commandsToRegister) { // Normalize the name the user might have passed in (e.g., 'demo:echo' vs '--demo:echo') const normalizedName = name.startsWith('--') ? name : `--${name}`; if (demoCommands[normalizedName]) { window.Invoker?.register?.(normalizedName, demoCommands[normalizedName]); } else { console.warn(`Invokers: Demo command "${name}" was requested but not found. Skipping registration.`); } } }