/** * Base Widget Class * * Foundation class for all Markitect UI widgets following the plugin architecture. * Provides core functionality for event handling, state management, and lifecycle. */ export class Widget extends EventTarget { constructor(options = {}) { super(); // Core properties this.id = options.id || `widget-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; this.container = options.container || document.body; this.config = { ...this.getDefaultConfig(), ...options }; // State management this.state = new Map(); this.isInitialized = false; this.isDestroyed = false; // Mixin support this.mixins = []; // Lifecycle hooks this.onInitialize = options.onInitialize || (() => {}); this.onDestroy = options.onDestroy || (() => {}); } /** * Initialize the widget */ async initialize() { if (this.isInitialized || this.isDestroyed) { return this; } try { await this.onInitialize(this); this.isInitialized = true; this.emit('initialized'); return this; } catch (error) { this.emit('error', { phase: 'initialize', error }); throw error; } } /** * Destroy the widget and clean up resources */ async destroy() { if (this.isDestroyed) { return; } try { await this.onDestroy(this); this.isDestroyed = true; this.emit('destroyed'); } catch (error) { this.emit('error', { phase: 'destroy', error }); throw error; } } /** * State management */ setState(key, value) { const oldValue = this.state.get(key); this.state.set(key, value); this.emit('state-changed', { key, value, oldValue }); } getState(key, defaultValue = null) { return this.state.get(key) ?? defaultValue; } /** * Event emission wrapper */ emit(eventType, data = {}) { const event = new CustomEvent(eventType, { detail: { widget: this, ...data } }); this.dispatchEvent(event); } /** * Apply mixin functionality */ applyMixin(mixin) { if (typeof mixin === 'object') { Object.assign(this, mixin); this.mixins.push(mixin); } return this; } /** * Default configuration (override in subclasses) */ getDefaultConfig() { return {}; } /** * Utility method for creating DOM elements with styling */ createElement(tag, options = {}) { const element = document.createElement(tag); if (options.className) { element.className = options.className; } if (options.textContent) { element.textContent = options.textContent; } if (options.innerHTML) { element.innerHTML = options.innerHTML; } if (options.style) { if (typeof options.style === 'string') { element.style.cssText = options.style; } else { Object.assign(element.style, options.style); } } if (options.attributes) { Object.entries(options.attributes).forEach(([key, value]) => { element.setAttribute(key, value); }); } return element; } }