@magnusbag/livets-core
Version:
TypeScript API layer for LiveTS framework
215 lines • 6.85 kB
JavaScript
"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