Files
markitect-main/capabilities/testdrive-jsui/js/widgets/base/Widget.js
tegwick ab3f0db86f feat: consolidate testdrive-jsui to capabilities and implement plugin self-declaration
## Major Changes
- Moved all testdrive-jsui assets from root to capabilities/testdrive-jsui/
- Consolidated directory structure: js/, static/css/, static/images/, static/templates/
- Implemented plugin self-declaration (get_plugin_source_dir, get_asset_paths)
- Removed hardcoded plugin discovery from rendering.py
- Updated all asset paths to be relative to capability root

## Architecture Improvements
- Single source of truth for all testdrive-jsui assets
- Plugin declares its own location (no hardcoded paths)
- Generic plugin discovery using hasattr check
- Clean separation: all JS in .js files, no code mixing
- Standalone capability ready for independent use

## Files Changed
- markitect/plugins/testdrive_jsui.py: Added self-declaration methods
- markitect/plugins/rendering.py: Removed hardcoded discovery
- capabilities/testdrive-jsui/README.md: Added standalone usage documentation
- Moved 17 asset files to consolidated structure
- Deleted obsolete /testdrive-jsui/ root directory

## Testing
- All 17 assets verified and working
- Tested via CLI: markitect md-render --engine testdrive-jsui
- Full document rendering successful

Prepares testdrive-jsui to become a git submodule with proper dependency management.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 23:42:54 +01:00

141 lines
3.5 KiB
JavaScript

/**
* 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;
}
}