eleva
Version:
A minimalist and lightweight, pure vanilla JavaScript frontend runtime framework.
354 lines • 14.7 kB
TypeScript
/**
* @typedef {Object} ComponentDefinition
* @property {function(ComponentContext): (Record<string, unknown>|Promise<Record<string, unknown>>)} [setup]
* Optional setup function that initializes the component's state and returns reactive data
* @property {(function(ComponentContext): string|Promise<string>)} template
* Required function that defines the component's HTML structure
* @property {(function(ComponentContext): string)|string} [style]
* Optional function or string that provides component-scoped CSS styles
* @property {Record<string, ComponentDefinition>} [children]
* Optional object defining nested child components
*/
/**
* @typedef {Object} ComponentContext
* @property {Record<string, unknown>} props
* Component properties passed during mounting
* @property {Emitter} emitter
* Event emitter instance for component event handling
* @property {function<T>(value: T): Signal<T>} signal
* Factory function to create reactive Signal instances
* @property {function(LifecycleHookContext): Promise<void>} [onBeforeMount]
* Hook called before component mounting
* @property {function(LifecycleHookContext): Promise<void>} [onMount]
* Hook called after component mounting
* @property {function(LifecycleHookContext): Promise<void>} [onBeforeUpdate]
* Hook called before component update
* @property {function(LifecycleHookContext): Promise<void>} [onUpdate]
* Hook called after component update
* @property {function(UnmountHookContext): Promise<void>} [onUnmount]
* Hook called during component unmounting
*/
/**
* @typedef {Object} LifecycleHookContext
* @property {HTMLElement} container
* The DOM element where the component is mounted
* @property {ComponentContext} context
* The component's reactive state and context data
*/
/**
* @typedef {Object} UnmountHookContext
* @property {HTMLElement} container
* The DOM element where the component is mounted
* @property {ComponentContext} context
* The component's reactive state and context data
* @property {{
* watchers: Array<() => void>, // Signal watcher cleanup functions
* listeners: Array<() => void>, // Event listener cleanup functions
* children: Array<MountResult> // Child component instances
* }} cleanup
* Object containing cleanup functions and instances
*/
/**
* @typedef {Object} MountResult
* @property {HTMLElement} container
* The DOM element where the component is mounted
* @property {ComponentContext} data
* The component's reactive state and context data
* @property {function(): Promise<void>} unmount
* Function to clean up and unmount the component
*/
/**
* @typedef {Object} ElevaPlugin
* @property {function(Eleva, Record<string, unknown>): void} install
* Function that installs the plugin into the Eleva instance
* @property {string} name
* Unique identifier name for the plugin
*/
/**
* @class 🧩 Eleva
* @classdesc A modern, signal-based component runtime framework that provides lifecycle hooks,
* scoped styles, and plugin support. Eleva manages component registration, plugin integration,
* event handling, and DOM rendering with a focus on performance and developer experience.
*
* @example
* // Basic component creation and mounting
* const app = new Eleva("myApp");
* app.component("myComponent", {
* setup: (ctx) => ({ count: ctx.signal(0) }),
* template: (ctx) => `<div>Hello ${ctx.props.name}</div>`
* });
* app.mount(document.getElementById("app"), "myComponent", { name: "World" });
*
* @example
* // Using lifecycle hooks
* app.component("lifecycleDemo", {
* setup: () => {
* return {
* onMount: ({ container, context }) => {
* console.log('Component mounted!');
* }
* };
* },
* template: `<div>Lifecycle Demo</div>`
* });
*/
export class Eleva {
/**
* Creates a new Eleva instance with the specified name and configuration.
*
* @public
* @param {string} name - The unique identifier name for this Eleva instance.
* @param {Record<string, unknown>} [config={}] - Optional configuration object for the instance.
* May include framework-wide settings and default behaviors.
* @throws {Error} If the name is not provided or is not a string.
* @returns {Eleva} A new Eleva instance.
*
* @example
* const app = new Eleva("myApp");
* app.component("myComponent", {
* setup: (ctx) => ({ count: ctx.signal(0) }),
* template: (ctx) => `<div>Hello ${ctx.props.name}!</div>`
* });
* app.mount(document.getElementById("app"), "myComponent", { name: "World" });
*
*/
constructor(name: string, config?: Record<string, unknown>);
/** @public {string} The unique identifier name for this Eleva instance */
public name: string;
/** @public {Object<string, unknown>} Optional configuration object for the Eleva instance */
public config: Record<string, unknown>;
/** @public {Emitter} Instance of the event emitter for handling component events */
public emitter: Emitter;
/** @public {typeof Signal} Static reference to the Signal class for creating reactive state */
public signal: typeof Signal;
/** @public {Renderer} Instance of the renderer for handling DOM updates and patching */
public renderer: Renderer;
/** @private {Map<string, ComponentDefinition>} Registry of all component definitions by name */
private _components;
/** @private {Map<string, ElevaPlugin>} Collection of installed plugin instances by name */
private _plugins;
/** @private {boolean} Flag indicating if the root component is currently mounted */
private _isMounted;
/** @private {number} Counter for generating unique component IDs */
private _componentCounter;
/**
* Integrates a plugin with the Eleva framework.
* The plugin's install function will be called with the Eleva instance and provided options.
* After installation, the plugin will be available for use by components.
*
* @public
* @param {ElevaPlugin} plugin - The plugin object which must have an `install` function.
* @param {Object<string, unknown>} [options={}] - Optional configuration options for the plugin.
* @returns {Eleva} The Eleva instance (for method chaining).
* @example
* app.use(myPlugin, { option1: "value1" });
*/
public use(plugin: ElevaPlugin, options?: {
[x: string]: unknown;
}): Eleva;
/**
* Registers a new component with the Eleva instance.
* The component will be available for mounting using its registered name.
*
* @public
* @param {string} name - The unique name of the component to register.
* @param {ComponentDefinition} definition - The component definition including setup, template, style, and children.
* @returns {Eleva} The Eleva instance (for method chaining).
* @throws {Error} If the component name is already registered.
* @example
* app.component("myButton", {
* template: (ctx) => `<button>${ctx.props.text}</button>`,
* style: `button { color: blue; }`
* });
*/
public component(name: string, definition: ComponentDefinition): Eleva;
/**
* Mounts a registered component to a DOM element.
* This will initialize the component, set up its reactive state, and render it to the DOM.
*
* @public
* @param {HTMLElement} container - The DOM element where the component will be mounted.
* @param {string|ComponentDefinition} compName - The name of the registered component or a direct component definition.
* @param {Object<string, unknown>} [props={}] - Optional properties to pass to the component.
* @returns {Promise<MountResult>}
* A Promise that resolves to an object containing:
* - container: The mounted component's container element
* - data: The component's reactive state and context
* - unmount: Function to clean up and unmount the component
* @throws {Error} If the container is not found, or component is not registered.
* @example
* const instance = await app.mount(document.getElementById("app"), "myComponent", { text: "Click me" });
* // Later...
* instance.unmount();
*/
public mount(container: HTMLElement, compName: string | ComponentDefinition, props?: {
[x: string]: unknown;
}): Promise<MountResult>;
/**
* Processes DOM elements for event binding based on attributes starting with "@".
* This method handles the event delegation system and ensures proper cleanup of event listeners.
*
* @private
* @param {HTMLElement} container - The container element in which to search for event attributes.
* @param {ComponentContext} context - The current component context containing event handler definitions.
* @param {Array<() => void>} listeners - Array to collect cleanup functions for each event listener.
* @returns {void}
*/
private _processEvents;
/**
* Injects scoped styles into the component's container.
* The styles are automatically prefixed to prevent style leakage to other components.
*
* @private
* @param {HTMLElement} container - The container element where styles should be injected.
* @param {string} compId - The component ID used to identify the style element.
* @param {(function(ComponentContext): string)|string} styleDef - The component's style definition (function or string).
* @param {ComponentContext} context - The current component context for style interpolation.
* @returns {void}
*/
private _injectStyles;
/**
* Extracts props from an element's attributes that start with the specified prefix.
* This method is used to collect component properties from DOM elements.
*
* @private
* @param {HTMLElement} element - The DOM element to extract props from
* @returns {Record<string, string>} An object containing the extracted props
* @example
* // For an element with attributes:
* // <div :name="John" :age="25">
* // Returns: { name: "John", age: "25" }
*/
private _extractProps;
/**
* Mounts all components within the parent component's container.
* This method handles mounting of explicitly defined children components.
*
* The mounting process follows these steps:
* 1. Cleans up any existing component instances
* 2. Mounts explicitly defined children components
*
* @private
* @param {HTMLElement} container - The container element to mount components in
* @param {Object<string, ComponentDefinition>} children - Map of selectors to component definitions for explicit children
* @param {Array<MountResult>} childInstances - Array to store all mounted component instances
* @returns {Promise<void>}
*
* @example
* // Explicit children mounting:
* const children = {
* 'UserProfile': UserProfileComponent,
* '#settings-panel': "settings-panel"
* };
*/
private _mountComponents;
}
export type ComponentDefinition = {
/**
* Optional setup function that initializes the component's state and returns reactive data
*/
setup?: ((arg0: ComponentContext) => (Record<string, unknown> | Promise<Record<string, unknown>>)) | undefined;
/**
* Required function that defines the component's HTML structure
*/
template: ((arg0: ComponentContext) => string | Promise<string>);
/**
* Optional function or string that provides component-scoped CSS styles
*/
style?: string | ((arg0: ComponentContext) => string) | undefined;
/**
* Optional object defining nested child components
*/
children?: Record<string, ComponentDefinition> | undefined;
};
export type ComponentContext = {
/**
* Component properties passed during mounting
*/
props: Record<string, unknown>;
/**
* Event emitter instance for component event handling
*/
emitter: Emitter;
/**
* <T>(value: T): Signal<T>} signal
* Factory function to create reactive Signal instances
*/
"": Function;
/**
* Hook called before component mounting
*/
onBeforeMount?: ((arg0: LifecycleHookContext) => Promise<void>) | undefined;
/**
* Hook called after component mounting
*/
onMount?: ((arg0: LifecycleHookContext) => Promise<void>) | undefined;
/**
* Hook called before component update
*/
onBeforeUpdate?: ((arg0: LifecycleHookContext) => Promise<void>) | undefined;
/**
* Hook called after component update
*/
onUpdate?: ((arg0: LifecycleHookContext) => Promise<void>) | undefined;
/**
* Hook called during component unmounting
*/
onUnmount?: ((arg0: UnmountHookContext) => Promise<void>) | undefined;
};
export type LifecycleHookContext = {
/**
* The DOM element where the component is mounted
*/
container: HTMLElement;
/**
* The component's reactive state and context data
*/
context: ComponentContext;
};
export type UnmountHookContext = {
/**
* The DOM element where the component is mounted
*/
container: HTMLElement;
/**
* The component's reactive state and context data
*/
context: ComponentContext;
/**
* Object containing cleanup functions and instances
*/
cleanup: {
watchers: Array<() => void>;
listeners: Array<() => void>;
children: Array<MountResult>;
};
};
export type MountResult = {
/**
* The DOM element where the component is mounted
*/
container: HTMLElement;
/**
* The component's reactive state and context data
*/
data: ComponentContext;
/**
* Function to clean up and unmount the component
*/
unmount: () => Promise<void>;
};
export type ElevaPlugin = {
/**
* Function that installs the plugin into the Eleva instance
*/
install: (arg0: Eleva, arg1: Record<string, unknown>) => void;
/**
* Unique identifier name for the plugin
*/
name: string;
};
import { Emitter } from "../modules/Emitter.js";
import { Signal } from "../modules/Signal.js";
import { Renderer } from "../modules/Renderer.js";
//# sourceMappingURL=Eleva.d.ts.map