UNPKG

@magnusbag/livets-core

Version:

TypeScript API layer for LiveTS framework

215 lines 6.85 kB
"use strict"; /** * LiveView base class - The foundation for all LiveTS components */ Object.defineProperty(exports, "__esModule", { value: true }); exports.LiveView = void 0; const uuid_1 = require("uuid"); const utils_1 = require("./utils"); class LiveView { constructor(props = {}) { this.state = {}; this.eventHandlers = new Map(); this.subscriptions = new Map(); this.componentId = props.id || (0, uuid_1.v4)(); this.props = props; this.metadata = { componentId: this.componentId, mounted: false, subscriptions: new Set() }; } // ===== State Management ===== /** * Updates the component state and triggers a re-render */ setState(updates) { const previousState = { ...this.state }; this.state = { ...this.state, ...updates }; // Trigger re-render if mounted if (this.metadata.mounted) { this.scheduleRerender(); } // Call updated lifecycle hook if it exists if (this.updated) { Promise.resolve(this.updated()).catch(error => { console.error(`Error in updated lifecycle for component ${this.componentId}:`, error); }); } } /** * Gets the current state (read-only) */ getState() { return Object.freeze({ ...this.state }); } // ===== Event Handling ===== /** * Handles events from the client * Override this method to handle custom events */ handleEvent(event, payload) { const handler = this.eventHandlers.get(event); if (handler) { return handler(event, payload); } // Default behavior: try to call a method with the event name const methodName = event; const method = this[methodName]; if (typeof method === 'function') { return method.call(this, payload); } console.warn(`No handler found for event '${event}' on component ${this.componentId}`); } /** * Registers an event handler */ on(event, handler) { this.eventHandlers.set(event, handler); } /** * Removes an event handler */ off(event) { this.eventHandlers.delete(event); } // ===== Pub/Sub Messaging ===== /** * Subscribes to a pub/sub channel */ subscribe(channel, handler) { this.subscriptions.set(channel, handler); this.metadata.subscriptions.add(channel); // TODO: Register with the Rust core engine console.log(`Component ${this.componentId} subscribed to channel: ${channel}`); } /** * Unsubscribes from a pub/sub channel */ unsubscribe(channel) { this.subscriptions.delete(channel); this.metadata.subscriptions.delete(channel); // TODO: Unregister with the Rust core engine console.log(`Component ${this.componentId} unsubscribed from channel: ${channel}`); } /** * Broadcasts a message to a pub/sub channel */ broadcast(channel, data) { const message = { channel, data, timestamp: Date.now() }; // TODO: Send through the Rust core engine console.log(`Component ${this.componentId} broadcasting to channel ${channel}:`, data); } /** * Handles incoming pub/sub messages */ handlePubSubMessage(message) { const handler = this.subscriptions.get(message.channel); if (handler) { Promise.resolve(handler(message.data)).catch(error => { console.error(`Error handling pub/sub message for channel ${message.channel} on component ${this.componentId}:`, error); }); } } // ===== Component Lifecycle Management ===== /** * Mounts the component (internal use) */ async _mount() { if (this.metadata.mounted) { return; } try { await Promise.resolve(this.mount()); this.metadata.mounted = true; } catch (error) { console.error(`Error mounting component ${this.componentId}:`, error); throw error; } } /** * Unmounts the component (internal use) */ async _unmount() { if (!this.metadata.mounted) { return; } try { // Clean up subscriptions for (const channel of this.metadata.subscriptions) { this.unsubscribe(channel); } // Call unmount lifecycle hook if it exists if (this.unmount) { await Promise.resolve(this.unmount()); } this.metadata.mounted = false; } catch (error) { console.error(`Error unmounting component ${this.componentId}:`, error); throw error; } } /** * Renders the component (internal use) */ _render() { if (!this.metadata.mounted) { throw new Error(`Cannot render unmounted component ${this.componentId}`); } try { const html = this.render(); // Inject reactive selectors before wrapping with component ID // Pass state keys to help identify reactive elements const stateKeys = Object.keys(this.state); const htmlWithSelectors = (0, utils_1.injectReactiveSelectorsSmart)(html, this.componentId, stateKeys); return this.wrapWithComponentId(htmlWithSelectors); } catch (error) { console.error(`Error rendering component ${this.componentId}:`, error); throw error; } } // ===== Utility Methods ===== /** * Gets the component ID */ getComponentId() { return this.componentId; } /** * Gets the component props */ getProps() { return Object.freeze({ ...this.props }); } /** * Checks if the component is mounted */ isMounted() { return this.metadata.mounted; } /** * Schedules a re-render (internal use) */ scheduleRerender() { // TODO: Communicate with the Rust core to trigger a re-render console.log(`Scheduling re-render for component ${this.componentId}`); } /** * Wraps the rendered HTML with component identification */ wrapWithComponentId(html) { // Add data attribute to identify this component // Handle leading whitespace by finding the first tag const wrappedHtml = html.replace(/^(\s*)<(\w+)([^>]*)>/, `$1<$2$3 data-livets-id="${this.componentId}">`); return wrappedHtml || `<div data-livets-id="${this.componentId}">${html}</div>`; } } exports.LiveView = LiveView; //# sourceMappingURL=live-view.js.map