javascript

examples

examples.js⚔
/**
 * Component Patterns - Examples
 *
 * Advanced patterns for building robust Web Components
 */

// ============================================
// EXAMPLE 1: Event Bus for Component Communication
// ============================================

class EventBus {
  static _events = new Map();
  static _onceEvents = new Set();

  /**
   * Subscribe to an event
   */
  static on(event, handler) {
    if (!this._events.has(event)) {
      this._events.set(event, new Set());
    }
    this._events.get(event).add(handler);

    return () => this.off(event, handler);
  }

  /**
   * Subscribe to an event (only once)
   */
  static once(event, handler) {
    const wrapper = (...args) => {
      this.off(event, wrapper);
      handler(...args);
    };
    this._onceEvents.add(wrapper);
    return this.on(event, wrapper);
  }

  /**
   * Unsubscribe from an event
   */
  static off(event, handler) {
    const handlers = this._events.get(event);
    if (handlers) {
      handlers.delete(handler);
    }
  }

  /**
   * Emit an event
   */
  static emit(event, data) {
    const handlers = this._events.get(event);
    if (handlers) {
      handlers.forEach((handler) => {
        try {
          handler(data);
        } catch (error) {
          console.error(`Error in event handler for ${event}:`, error);
        }
      });
    }
  }

  /**
   * Clear all handlers for an event
   */
  static clear(event) {
    this._events.delete(event);
  }

  /**
   * Clear all events
   */
  static clearAll() {
    this._events.clear();
  }
}

// Components using EventBus
class SenderComponent extends HTMLElement {
  connectedCallback() {
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = `
            <button>Send Message</button>
        `;

    this.shadowRoot.querySelector('button').addEventListener('click', () => {
      EventBus.emit('message', {
        from: 'SenderComponent',
        text: 'Hello from sender!',
        timestamp: Date.now(),
      });
    });
  }
}

class ReceiverComponent extends HTMLElement {
  connectedCallback() {
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = `
            <div class="messages"></div>
        `;

    this._unsubscribe = EventBus.on('message', (data) => {
      const messages = this.shadowRoot.querySelector('.messages');
      messages.innerHTML += `<p>${data.text}</p>`;
    });
  }

  disconnectedCallback() {
    if (this._unsubscribe) {
      this._unsubscribe();
    }
  }
}

customElements.define('sender-component', SenderComponent);
customElements.define('receiver-component', ReceiverComponent);

// ============================================
// EXAMPLE 2: Reactive State with Proxy
// ============================================

function createReactiveState(initialState, onChange) {
  const handlers = {
    set(target, property, value, receiver) {
      const oldValue = target[property];
      const result = Reflect.set(target, property, value, receiver);

      if (oldValue !== value) {
        onChange({
          property,
          oldValue,
          newValue: value,
          state: target,
        });
      }

      return result;
    },

    deleteProperty(target, property) {
      const oldValue = target[property];
      const result = Reflect.deleteProperty(target, property);

      onChange({
        property,
        oldValue,
        newValue: undefined,
        state: target,
        deleted: true,
      });

      return result;
    },
  };

  return new Proxy({ ...initialState }, handlers);
}

class ReactiveCounter extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });

    this.state = createReactiveState({ count: 0 }, () => this.render());
  }

  connectedCallback() {
    this.render();
  }

  increment() {
    this.state.count++;
  }

  decrement() {
    this.state.count--;
  }

  render() {
    this.shadowRoot.innerHTML = `
            <style>
                :host {
                    display: inline-flex;
                    align-items: center;
                    gap: 12px;
                    font-family: system-ui, sans-serif;
                }
                button {
                    width: 36px;
                    height: 36px;
                    border: none;
                    border-radius: 50%;
                    background: #3b82f6;
                    color: white;
                    font-size: 18px;
                    cursor: pointer;
                }
                button:hover { background: #2563eb; }
                .count {
                    min-width: 40px;
                    text-align: center;
                    font-size: 24px;
                    font-weight: bold;
                }
            </style>
            <button class="decrement">āˆ’</button>
            <span class="count">${this.state.count}</span>
            <button class="increment">+</button>
        `;

    this.shadowRoot.querySelector('.increment').onclick = () =>
      this.increment();
    this.shadowRoot.querySelector('.decrement').onclick = () =>
      this.decrement();
  }
}

customElements.define('reactive-counter', ReactiveCounter);

// ============================================
// EXAMPLE 3: Global Store Pattern
// ============================================

class Store {
  constructor(initialState = {}) {
    this._state = initialState;
    this._subscribers = new Map();
    this._nextId = 0;
  }

  getState() {
    return { ...this._state };
  }

  setState(newState) {
    const prevState = this._state;
    this._state = { ...this._state, ...newState };
    this._notify(prevState);
  }

  subscribe(selector, callback) {
    const id = this._nextId++;
    this._subscribers.set(id, { selector, callback });

    // Return unsubscribe function
    return () => this._subscribers.delete(id);
  }

  _notify(prevState) {
    this._subscribers.forEach(({ selector, callback }) => {
      const prev = selector ? selector(prevState) : prevState;
      const curr = selector ? selector(this._state) : this._state;

      if (prev !== curr) {
        callback(curr, prev);
      }
    });
  }
}

// Create global store instance
const globalStore = new Store({
  user: null,
  theme: 'light',
  notifications: [],
});

// Component connected to store
class StoreConnected extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this._unsubscribers = [];
  }

  connectedCallback() {
    // Subscribe to specific state slice
    const unsubTheme = globalStore.subscribe(
      (state) => state.theme,
      (theme) => this.onThemeChange(theme)
    );
    this._unsubscribers.push(unsubTheme);

    this.render();
  }

  disconnectedCallback() {
    this._unsubscribers.forEach((unsub) => unsub());
  }

  onThemeChange(theme) {
    this.setAttribute('data-theme', theme);
    this.render();
  }

  toggleTheme() {
    const current = globalStore.getState().theme;
    globalStore.setState({ theme: current === 'light' ? 'dark' : 'light' });
  }

  render() {
    const { theme } = globalStore.getState();
    this.shadowRoot.innerHTML = `
            <style>
                :host([data-theme="dark"]) .container {
                    background: #1f2937;
                    color: white;
                }
                .container {
                    padding: 20px;
                    border-radius: 8px;
                    background: #f3f4f6;
                    transition: all 0.3s;
                }
            </style>
            <div class="container">
                <p>Current theme: ${theme}</p>
                <button>Toggle Theme</button>
            </div>
        `;

    this.shadowRoot.querySelector('button').onclick = () => this.toggleTheme();
  }
}

customElements.define('store-connected', StoreConnected);

// ============================================
// EXAMPLE 4: Mixin Pattern
// ============================================

// Logger Mixin
const LoggerMixin = (Base) =>
  class extends Base {
    log(level, message) {
      const timestamp = new Date().toISOString();
      console[level](`[${timestamp}] [${this.tagName}] ${message}`);
    }

    logInfo(message) {
      this.log('info', message);
    }
    logWarn(message) {
      this.log('warn', message);
    }
    logError(message) {
      this.log('error', message);
    }
  };

// Event Emitter Mixin
const EventEmitterMixin = (Base) =>
  class extends Base {
    emit(eventName, detail = {}, options = {}) {
      const event = new CustomEvent(eventName, {
        detail,
        bubbles: options.bubbles ?? true,
        composed: options.composed ?? true,
        cancelable: options.cancelable ?? false,
      });
      return this.dispatchEvent(event);
    }
  };

// State Mixin
const StateMixin = (Base) =>
  class extends Base {
    _state = {};

    get state() {
      return { ...this._state };
    }

    setState(newState) {
      const prevState = this._state;
      this._state = { ...this._state, ...newState };

      if (typeof this.stateChanged === 'function') {
        this.stateChanged(prevState, this._state);
      }

      if (typeof this.render === 'function') {
        this.scheduleRender();
      }
    }

    scheduleRender() {
      if (!this._renderScheduled) {
        this._renderScheduled = true;
        requestAnimationFrame(() => {
          this._renderScheduled = false;
          this.render();
        });
      }
    }
  };

// Cleanup Mixin
const CleanupMixin = (Base) =>
  class extends Base {
    _cleanupFunctions = [];

    addCleanup(fn) {
      this._cleanupFunctions.push(fn);
    }

    cleanup() {
      this._cleanupFunctions.forEach((fn) => fn());
      this._cleanupFunctions = [];
    }

    disconnectedCallback() {
      this.cleanup();
      if (super.disconnectedCallback) {
        super.disconnectedCallback();
      }
    }
  };

// Combined component using mixins
class MixedComponent extends CleanupMixin(
  StateMixin(EventEmitterMixin(LoggerMixin(HTMLElement)))
) {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.setState({ clicks: 0 });
  }

  connectedCallback() {
    this.logInfo('Connected');
    this.render();

    // Event listener with cleanup
    const handler = (e) => this.logInfo('Document clicked');
    document.addEventListener('click', handler);
    this.addCleanup(() => document.removeEventListener('click', handler));
  }

  handleClick() {
    this.setState({ clicks: this.state.clicks + 1 });
    this.emit('click-count', { count: this.state.clicks });
  }

  render() {
    this.shadowRoot.innerHTML = `
            <style>
                button {
                    padding: 10px 20px;
                    border: none;
                    border-radius: 6px;
                    background: #8b5cf6;
                    color: white;
                    cursor: pointer;
                }
            </style>
            <button>Clicks: ${this.state.clicks}</button>
        `;

    this.shadowRoot.querySelector('button').onclick = () => this.handleClick();
  }
}

customElements.define('mixed-component', MixedComponent);

// ============================================
// EXAMPLE 5: Form-Associated Component
// ============================================

class CustomInput extends HTMLElement {
  static formAssociated = true;

  constructor() {
    super();
    this._internals = this.attachInternals();
    this.attachShadow({ mode: 'open' });
    this._value = '';
  }

  static get observedAttributes() {
    return ['value', 'placeholder', 'required', 'disabled', 'pattern'];
  }

  get value() {
    return this._value;
  }
  set value(v) {
    this._value = v;
    this._internals.setFormValue(v);
    this.validate();
  }

  get validity() {
    return this._internals.validity;
  }
  get validationMessage() {
    return this._internals.validationMessage;
  }

  connectedCallback() {
    this.render();
  }

  attributeChangedCallback(name, oldVal, newVal) {
    if (name === 'value') {
      this._value = newVal;
    }
    this.render();
  }

  validate() {
    const input = this.shadowRoot?.querySelector('input');
    if (!input) return true;

    if (this.hasAttribute('required') && !this._value) {
      this._internals.setValidity(
        { valueMissing: true },
        'This field is required',
        input
      );
      return false;
    }

    const pattern = this.getAttribute('pattern');
    if (pattern && !new RegExp(pattern).test(this._value)) {
      this._internals.setValidity(
        { patternMismatch: true },
        'Please match the requested format',
        input
      );
      return false;
    }

    this._internals.setValidity({});
    return true;
  }

  formResetCallback() {
    this.value = '';
    this.render();
  }

  formStateRestoreCallback(state) {
    this.value = state;
  }

  formDisabledCallback(disabled) {
    this.toggleAttribute('disabled', disabled);
    this.render();
  }

  render() {
    const disabled = this.hasAttribute('disabled');
    const placeholder = this.getAttribute('placeholder') || '';

    this.shadowRoot.innerHTML = `
            <style>
                :host {
                    display: block;
                }
                input {
                    width: 100%;
                    padding: 10px 12px;
                    border: 2px solid #d1d5db;
                    border-radius: 6px;
                    font-size: 14px;
                    box-sizing: border-box;
                    transition: border-color 0.2s;
                }
                input:focus {
                    outline: none;
                    border-color: #3b82f6;
                }
                input:invalid {
                    border-color: #ef4444;
                }
                input:disabled {
                    background: #f3f4f6;
                    cursor: not-allowed;
                }
            </style>
            <input 
                type="text" 
                .value="${this._value}"
                placeholder="${placeholder}"
                ${disabled ? 'disabled' : ''}
            />
        `;

    const input = this.shadowRoot.querySelector('input');
    input.value = this._value;

    input.addEventListener('input', (e) => {
      this.value = e.target.value;
      this.dispatchEvent(new Event('input', { bubbles: true }));
    });

    input.addEventListener('change', (e) => {
      this.dispatchEvent(new Event('change', { bubbles: true }));
    });
  }
}

customElements.define('custom-input', CustomInput);

// ============================================
// EXAMPLE 6: Accessible Component
// ============================================

class AccessibleTabs extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this._selectedIndex = 0;
  }

  connectedCallback() {
    this.render();
    this.setupAccessibility();
  }

  get selectedIndex() {
    return this._selectedIndex;
  }
  set selectedIndex(value) {
    this._selectedIndex = value;
    this.updateSelection();
  }

  setupAccessibility() {
    const tablist = this.shadowRoot.querySelector('[role="tablist"]');

    tablist.addEventListener('keydown', (e) => {
      const tabs = this.shadowRoot.querySelectorAll('[role="tab"]');
      const currentIndex = this._selectedIndex;

      switch (e.key) {
        case 'ArrowRight':
        case 'ArrowDown':
          e.preventDefault();
          this.selectedIndex = (currentIndex + 1) % tabs.length;
          tabs[this.selectedIndex].focus();
          break;

        case 'ArrowLeft':
        case 'ArrowUp':
          e.preventDefault();
          this.selectedIndex = (currentIndex - 1 + tabs.length) % tabs.length;
          tabs[this.selectedIndex].focus();
          break;

        case 'Home':
          e.preventDefault();
          this.selectedIndex = 0;
          tabs[0].focus();
          break;

        case 'End':
          e.preventDefault();
          this.selectedIndex = tabs.length - 1;
          tabs[tabs.length - 1].focus();
          break;
      }
    });
  }

  updateSelection() {
    const tabs = this.shadowRoot.querySelectorAll('[role="tab"]');
    const panels = this.shadowRoot.querySelectorAll('[role="tabpanel"]');

    tabs.forEach((tab, i) => {
      const selected = i === this._selectedIndex;
      tab.setAttribute('aria-selected', selected);
      tab.setAttribute('tabindex', selected ? '0' : '-1');
    });

    panels.forEach((panel, i) => {
      panel.hidden = i !== this._selectedIndex;
    });

    this.dispatchEvent(
      new CustomEvent('tab-change', {
        detail: { index: this._selectedIndex },
      })
    );
  }

  render() {
    const panels = Array.from(this.children);
    const tabs = panels.map((panel, i) => ({
      label: panel.getAttribute('data-label') || `Tab ${i + 1}`,
      id: `tab-${i}`,
    }));

    this.shadowRoot.innerHTML = `
            <style>
                :host {
                    display: block;
                    font-family: system-ui, sans-serif;
                }
                [role="tablist"] {
                    display: flex;
                    gap: 4px;
                    border-bottom: 2px solid #e5e7eb;
                }
                [role="tab"] {
                    padding: 12px 20px;
                    border: none;
                    background: none;
                    cursor: pointer;
                    font-size: 14px;
                    color: #64748b;
                    border-bottom: 2px solid transparent;
                    margin-bottom: -2px;
                    transition: all 0.2s;
                }
                [role="tab"]:hover {
                    color: #3b82f6;
                }
                [role="tab"][aria-selected="true"] {
                    color: #3b82f6;
                    border-bottom-color: #3b82f6;
                }
                [role="tab"]:focus {
                    outline: 2px solid #3b82f6;
                    outline-offset: -2px;
                }
                [role="tabpanel"] {
                    padding: 20px;
                }
                [role="tabpanel"][hidden] {
                    display: none;
                }
            </style>
            
            <div role="tablist" aria-label="Tabs">
                ${tabs
                  .map(
                    (tab, i) => `
                    <button 
                        role="tab"
                        id="${tab.id}"
                        aria-selected="${i === 0}"
                        aria-controls="panel-${i}"
                        tabindex="${i === 0 ? 0 : -1}"
                    >${tab.label}</button>
                `
                  )
                  .join('')}
            </div>
            
            ${panels
              .map(
                (_, i) => `
                <div 
                    role="tabpanel"
                    id="panel-${i}"
                    aria-labelledby="tab-${i}"
                    ${i !== 0 ? 'hidden' : ''}
                >
                    <slot name="panel-${i}"></slot>
                </div>
            `
              )
              .join('')}
        `;

    // Move children to named slots
    panels.forEach((panel, i) => {
      panel.setAttribute('slot', `panel-${i}`);
    });

    // Add click handlers
    this.shadowRoot.querySelectorAll('[role="tab"]').forEach((tab, i) => {
      tab.addEventListener('click', () => {
        this.selectedIndex = i;
      });
    });
  }
}

customElements.define('accessible-tabs', AccessibleTabs);

// ============================================
// EXAMPLE 7: Lazy Loading Component
// ============================================

class LazyImage extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this._loaded = false;
    this._observer = null;
  }

  static get observedAttributes() {
    return ['src', 'alt', 'width', 'height'];
  }

  connectedCallback() {
    this.render();
    this.setupObserver();
  }

  disconnectedCallback() {
    if (this._observer) {
      this._observer.disconnect();
    }
  }

  setupObserver() {
    this._observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting && !this._loaded) {
            this.loadImage();
          }
        });
      },
      { rootMargin: '50px' }
    );

    this._observer.observe(this);
  }

  loadImage() {
    const src = this.getAttribute('src');
    if (!src) return;

    const img = this.shadowRoot.querySelector('img');
    const placeholder = this.shadowRoot.querySelector('.placeholder');

    img.onload = () => {
      this._loaded = true;
      img.classList.add('loaded');
      placeholder?.remove();
      this._observer?.disconnect();
    };

    img.onerror = () => {
      placeholder.textContent = 'Failed to load';
    };

    img.src = src;
  }

  render() {
    const width = this.getAttribute('width') || 'auto';
    const height = this.getAttribute('height') || 'auto';
    const alt = this.getAttribute('alt') || '';

    this.shadowRoot.innerHTML = `
            <style>
                :host {
                    display: inline-block;
                    position: relative;
                    overflow: hidden;
                }
                .placeholder {
                    position: absolute;
                    inset: 0;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    background: #f3f4f6;
                    color: #9ca3af;
                }
                img {
                    display: block;
                    width: ${width};
                    height: ${height};
                    opacity: 0;
                    transition: opacity 0.3s ease;
                }
                img.loaded {
                    opacity: 1;
                }
            </style>
            <div class="placeholder">Loading...</div>
            <img alt="${alt}" />
        `;
  }
}

customElements.define('lazy-image', LazyImage);

// ============================================
// EXAMPLE 8: Component with Async Data
// ============================================

class AsyncDataComponent extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this._state = 'idle';
    this._data = null;
    this._error = null;
  }

  static get observedAttributes() {
    return ['url', 'auto-load'];
  }

  connectedCallback() {
    this.render();

    if (this.hasAttribute('auto-load')) {
      this.load();
    }
  }

  attributeChangedCallback(name, oldVal, newVal) {
    if (name === 'url' && oldVal !== newVal && this.hasAttribute('auto-load')) {
      this.load();
    }
  }

  async load() {
    const url = this.getAttribute('url');
    if (!url) return;

    this._state = 'loading';
    this._error = null;
    this.render();

    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error(`HTTP ${response.status}`);

      this._data = await response.json();
      this._state = 'success';
    } catch (error) {
      this._error = error.message;
      this._state = 'error';
    }

    this.render();
    this.dispatchEvent(
      new CustomEvent('load-complete', {
        detail: { state: this._state, data: this._data, error: this._error },
      })
    );
  }

  retry() {
    this.load();
  }

  render() {
    const templates = {
      idle: `<p>Click load to fetch data</p><button class="load">Load</button>`,
      loading: `<p>Loading...</p>`,
      error: `
                <p style="color: #ef4444;">Error: ${this._error}</p>
                <button class="retry">Retry</button>
            `,
      success: `
                <pre style="background: #f3f4f6; padding: 12px; border-radius: 6px; overflow: auto;">
${JSON.stringify(this._data, null, 2)}
                </pre>
            `,
    };

    this.shadowRoot.innerHTML = `
            <style>
                :host {
                    display: block;
                    font-family: system-ui, sans-serif;
                }
                button {
                    padding: 8px 16px;
                    border: none;
                    border-radius: 4px;
                    background: #3b82f6;
                    color: white;
                    cursor: pointer;
                }
            </style>
            ${templates[this._state]}
        `;

    this.shadowRoot
      .querySelector('.load')
      ?.addEventListener('click', () => this.load());
    this.shadowRoot
      .querySelector('.retry')
      ?.addEventListener('click', () => this.retry());
  }
}

customElements.define('async-data', AsyncDataComponent);

// ============================================
// EXAMPLE 9: Debounced Render Component
// ============================================

class DebouncedRender extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this._renderScheduled = false;
    this._updates = 0;
    this._renders = 0;
  }

  connectedCallback() {
    this.render();

    // Simulate rapid updates
    const button = document.createElement('button');
    button.textContent = 'Trigger 100 updates';
    button.onclick = () => this.simulateRapidUpdates();
    this.shadowRoot.appendChild(button);
  }

  scheduleRender() {
    this._updates++;

    if (!this._renderScheduled) {
      this._renderScheduled = true;
      requestAnimationFrame(() => {
        this._renderScheduled = false;
        this._renders++;
        this.render();
      });
    }
  }

  simulateRapidUpdates() {
    this._updates = 0;
    this._renders = 0;

    for (let i = 0; i < 100; i++) {
      this.scheduleRender();
    }

    // Show results after render
    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        alert(`Updates: ${this._updates}, Actual Renders: ${this._renders}`);
      });
    });
  }

  render() {
    this.shadowRoot.innerHTML = `
            <style>
                :host { display: block; font-family: system-ui; }
                button { margin-top: 12px; }
            </style>
            <p>Updates: ${this._updates}, Renders: ${this._renders}</p>
        `;
  }
}

customElements.define('debounced-render', DebouncedRender);

// ============================================
// EXAMPLE 10: Component Testing Utilities
// ============================================

class ComponentTestUtils {
  /**
   * Wait for component to be defined
   */
  static async whenDefined(tagName) {
    return customElements.whenDefined(tagName);
  }

  /**
   * Create and attach component to DOM
   */
  static async create(tagName, props = {}, container = document.body) {
    await this.whenDefined(tagName);

    const element = document.createElement(tagName);

    // Set attributes
    Object.entries(props).forEach(([key, value]) => {
      if (typeof value === 'boolean') {
        if (value) element.setAttribute(key, '');
      } else {
        element.setAttribute(key, value);
      }
    });

    container.appendChild(element);

    // Wait for next frame for rendering
    await new Promise((resolve) => requestAnimationFrame(resolve));

    return element;
  }

  /**
   * Query shadow DOM
   */
  static shadowQuery(element, selector) {
    return element.shadowRoot?.querySelector(selector);
  }

  /**
   * Simulate click
   */
  static click(element) {
    element.dispatchEvent(new MouseEvent('click', { bubbles: true }));
  }

  /**
   * Simulate input
   */
  static input(element, value) {
    element.value = value;
    element.dispatchEvent(new Event('input', { bubbles: true }));
  }

  /**
   * Wait for event
   */
  static waitForEvent(element, eventName, timeout = 1000) {
    return new Promise((resolve, reject) => {
      const timer = setTimeout(() => {
        reject(new Error(`Timeout waiting for ${eventName}`));
      }, timeout);

      element.addEventListener(
        eventName,
        (e) => {
          clearTimeout(timer);
          resolve(e);
        },
        { once: true }
      );
    });
  }

  /**
   * Cleanup
   */
  static cleanup(element) {
    element?.parentNode?.removeChild(element);
  }
}

// ============================================
// USAGE DEMONSTRATION
// ============================================

function demonstratePatterns() {
  console.log('=== Component Patterns Demonstration ===\n');

  console.log('Patterns demonstrated:');
  console.log('1. EventBus - Cross-component communication');
  console.log('2. Reactive State - Proxy-based reactivity');
  console.log('3. Global Store - Centralized state management');
  console.log('4. Mixins - Composable functionality');
  console.log('5. Form-Associated - Native form integration');
  console.log('6. Accessible Tabs - ARIA support');
  console.log('7. Lazy Loading - Performance optimization');
  console.log('8. Async Data - Data fetching pattern');
  console.log('9. Debounced Render - Batched updates');
  console.log('10. Test Utilities - Testing helpers');

  console.log('\nComponents:');
  const components = [
    'sender-component',
    'receiver-component',
    'reactive-counter',
    'store-connected',
    'mixed-component',
    'custom-input',
    'accessible-tabs',
    'lazy-image',
    'async-data',
    'debounced-render',
  ];

  components.forEach((name) => console.log(`  <${name}>`));
}

// Export for module usage
if (typeof module !== 'undefined' && module.exports) {
  module.exports = {
    EventBus,
    Store,
    globalStore,
    createReactiveState,
    ComponentTestUtils,
    demonstratePatterns,
  };
}
Examples - JavaScript Tutorial | DeepML