137 lines
4.1 KiB
JavaScript
137 lines
4.1 KiB
JavaScript
/**
|
|
* el-ui — Activation-based frontend framework.
|
|
*
|
|
* State is an Engram graph. Reactivity is spreading activation.
|
|
*
|
|
* Core exports:
|
|
* Graph — in-browser Engram graph (nodes, edges, activation)
|
|
* Renderer — DOM mounting and patching
|
|
* Router — graph-based routing
|
|
* Component — base class for el-ui components
|
|
* mount() — attach a component to the DOM
|
|
*
|
|
* @module el-ui
|
|
*/
|
|
|
|
export { Graph } from './graph.js';
|
|
export { Renderer } from './renderer.js';
|
|
export { Router } from './router.js';
|
|
export {
|
|
spreadActivation,
|
|
activationStrength,
|
|
reachableNodes,
|
|
PRUNE_THRESHOLD,
|
|
} from './activation.js';
|
|
|
|
import { Graph } from './graph.js';
|
|
import { Renderer } from './renderer.js';
|
|
|
|
/**
|
|
* Base class for all el-ui components.
|
|
*
|
|
* Subclasses override:
|
|
* render() → returns HTML string
|
|
* onMount() → called after first mount (optional)
|
|
*
|
|
* Subclasses call:
|
|
* setState(name, value) → update state, trigger activation, patch DOM
|
|
*/
|
|
export class Component {
|
|
constructor(props = {}) {
|
|
/** @type {Graph} */
|
|
this._graph = new Graph();
|
|
|
|
/** @type {Record<string, string>} state node IDs */
|
|
this._stateNodes = {};
|
|
|
|
/** @type {Record<string, any>} current state values */
|
|
this._state = {};
|
|
|
|
/** @type {Record<string, any>} current prop values */
|
|
this.props = props;
|
|
|
|
/** @type {Renderer|null} */
|
|
this._renderer = null;
|
|
}
|
|
|
|
/**
|
|
* Update a named state variable.
|
|
* Triggers spreading activation in the graph, then patches the DOM.
|
|
*
|
|
* @param {string} name
|
|
* @param {any} value
|
|
*/
|
|
setState(name, value) {
|
|
if (this._stateNodes[name] !== undefined) {
|
|
this._graph.update(this._stateNodes[name], value);
|
|
// Graph.update() notifies subscribers, which call _renderer.patch().
|
|
// Belt-and-suspenders: also patch directly in case no subscribers yet.
|
|
if (this._renderer) this._renderer.patch();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render a child component inline, sharing the parent's activation graph.
|
|
* Used by the compiler to embed child components in template strings.
|
|
*
|
|
* @param {typeof Component} ComponentClass
|
|
* @param {Record<string, any>} props
|
|
* @returns {string}
|
|
*/
|
|
_child(ComponentClass, props = {}) {
|
|
try {
|
|
const instance = new ComponentClass(props);
|
|
// Share the parent graph so child state participates in the same
|
|
// activation surface. Child state changes can propagate upward.
|
|
instance._graph = this._graph;
|
|
return instance.render();
|
|
} catch (e) {
|
|
console.warn(`el-ui: failed to render child ${ComponentClass?.name}`, e);
|
|
return `<!-- el-ui: render error in ${ComponentClass?.name} -->`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Override to return the component's HTML string.
|
|
* All state and props are accessible via this._state / this.props.
|
|
* @returns {string}
|
|
*/
|
|
render() {
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Called once after the component is first mounted to the DOM.
|
|
* Override for side effects (timers, fetch calls, subscriptions).
|
|
*/
|
|
onMount() {}
|
|
}
|
|
|
|
/**
|
|
* Mount a component to a DOM element and start the el-ui runtime.
|
|
*
|
|
* @param {typeof Component} ComponentClass the component class to mount
|
|
* @param {string} selector CSS selector for the root element
|
|
* @param {Record<string, any>} [props={}] initial props
|
|
* @returns {Component} the live component instance
|
|
*
|
|
* @example
|
|
* import { mount } from './el-ui.js';
|
|
* import { Counter } from './counter.js';
|
|
* mount(Counter, '#app');
|
|
*/
|
|
export function mount(ComponentClass, selector, props = {}) {
|
|
const root = document.querySelector(selector);
|
|
if (!root) {
|
|
throw new Error(`el-ui: no element found for selector '${selector}'`);
|
|
}
|
|
|
|
const component = new ComponentClass(props);
|
|
const renderer = new Renderer(root, component);
|
|
component._renderer = renderer;
|
|
renderer.mount();
|
|
return component;
|
|
}
|
|
|
|
export default { mount, Component, Graph, Renderer };
|