/** * UI Widget Base Class * * Extends Widget with DOM manipulation and visual functionality. * Base for all widgets that render UI elements. */ import { Widget } from './Widget.js'; export class UIWidget extends Widget { constructor(options = {}) { super(options); // UI properties this.element = null; this.isVisible = false; this.isRendered = false; this.theme = options.theme || 'default'; this.cssClasses = new Set(['markitect-widget']); // Animation support this.animationDuration = options.animationDuration || 300; this.enableAnimations = options.enableAnimations !== false; } /** * Render the widget to DOM (abstract method) */ async render() { throw new Error('render() method must be implemented by subclass'); } /** * Show the widget */ async show(options = {}) { if (!this.isRendered) { await this.render(); } if (this.isVisible) { return this; } this.isVisible = true; if (this.element) { if (this.enableAnimations && !options.immediate) { await this.animateShow(); } else { this.element.style.display = ''; } } this.emit('shown'); return this; } /** * Hide the widget */ async hide(options = {}) { if (!this.isVisible) { return this; } this.isVisible = false; if (this.element) { if (this.enableAnimations && !options.immediate) { await this.animateHide(); } else { this.element.style.display = 'none'; } } this.emit('hidden'); return this; } /** * Toggle visibility */ async toggle(options = {}) { return this.isVisible ? this.hide(options) : this.show(options); } /** * Show animation (override for custom animations) */ async animateShow() { if (!this.element) return; return new Promise(resolve => { this.element.style.transition = `opacity ${this.animationDuration}ms ease-in-out`; this.element.style.opacity = '0'; this.element.style.display = ''; // Force reflow this.element.offsetHeight; this.element.style.opacity = '1'; setTimeout(() => { this.element.style.transition = ''; resolve(); }, this.animationDuration); }); } /** * Hide animation (override for custom animations) */ async animateHide() { if (!this.element) return; return new Promise(resolve => { this.element.style.transition = `opacity ${this.animationDuration}ms ease-in-out`; this.element.style.opacity = '0'; setTimeout(() => { this.element.style.display = 'none'; this.element.style.transition = ''; this.element.style.opacity = ''; resolve(); }, this.animationDuration); }); } /** * CSS class management */ addClass(className) { this.cssClasses.add(className); if (this.element) { this.element.classList.add(className); } return this; } removeClass(className) { this.cssClasses.delete(className); if (this.element) { this.element.classList.remove(className); } return this; } hasClass(className) { return this.cssClasses.has(className); } /** * Apply theme styling */ applyTheme(themeName) { const oldTheme = this.theme; this.theme = themeName; this.removeClass(`theme-${oldTheme}`); this.addClass(`theme-${themeName}`); this.emit('theme-changed', { oldTheme, newTheme: themeName }); return this; } /** * Find child element by selector */ findElement(selector) { return this.element ? this.element.querySelector(selector) : null; } /** * Find all child elements by selector */ findElements(selector) { return this.element ? this.element.querySelectorAll(selector) : []; } /** * Override destroy to clean up DOM */ async destroy() { if (this.element && this.element.parentNode) { this.element.parentNode.removeChild(this.element); } this.element = null; this.isRendered = false; this.isVisible = false; await super.destroy(); } /** * Apply all CSS classes to element */ applyCSSClasses(element = this.element) { if (element) { element.className = Array.from(this.cssClasses).join(' '); } } /** * Default configuration for UI widgets */ getDefaultConfig() { return { ...super.getDefaultConfig(), theme: 'default', animationDuration: 300, enableAnimations: true }; } }