import { AvenxComponent } from './AvenxComponent.js';
/**
* AvenxPage is a specialized component that can host child components.
* It automatically mounts child components defined in its template via [data-avenx-comp].
*/
export class AvenxPage extends AvenxComponent {
/** @type {Map<string, typeof AvenxComponent>} @private */
#componentRegistry;
/** @type {Map<Element, AvenxComponent>} @private */
#childComponents = new Map();
/**
* @param {Object} initialState - Initial state.
* @param {Object} computed - Computed properties.
* @param {Object} bridges - Shared bridges.
* @param {string} template - HTML template.
* @param {Object} methods - Component methods.
* @param {Map<string, typeof AvenxComponent>} componentRegistry - Registry of available components.
*/
constructor(initialState = {}, computed = {}, bridges = {}, template = '', methods = {}, componentRegistry = new Map(), props = {}) {
super(initialState, computed, bridges, template, methods, props);
this.#componentRegistry = componentRegistry;
}
/**
* Updates the page and then mounts/updates child components.
*/
update() {
super.update();
this.#mountChildComponents();
}
/**
* Unmounts the page and all child components.
*/
unmount() {
for (const compInstance of this.#childComponents.values()) {
if (typeof compInstance.unmount === 'function') {
compInstance.unmount();
}
}
this.#childComponents.clear();
super.unmount();
}
/**
* Finds all mount points for child components and initializes or updates them.
* @private
*/
#mountChildComponents() {
const root = this._getElement();
if (!root) return;
const mountPoints = root.querySelectorAll('[data-avenx-comp]');
const currentElements = new Set(mountPoints);
// 1. Clean up/unmount child components whose elements are no longer in the DOM/page
for (const [el, compInstance] of this.#childComponents.entries()) {
if (!currentElements.has(el) || !root.contains(el)) {
if (typeof compInstance.unmount === 'function') {
compInstance.unmount();
}
this.#childComponents.delete(el);
}
}
// 2. Instantiate new components or update existing ones
mountPoints.forEach(el => {
const compName = el.getAttribute('data-avenx-comp');
const CompClass = this.#componentRegistry.get(compName);
if (CompClass) {
// Extract props from element's data-props-* attributes
const props = {};
for (const attr of el.attributes) {
if (attr.name.startsWith('data-props-')) {
const propName = attr.name.slice('data-props-'.length);
try {
props[propName] = this._evaluate(attr.value);
} catch (e) {
console.warn(`[AvenxPage] Failed to evaluate prop expression: ${attr.value}`, e);
}
}
}
if (this.#childComponents.has(el)) {
const compInstance = this.#childComponents.get(el);
if (typeof compInstance.setProps === 'function') {
compInstance.setProps(props);
} else if (typeof compInstance.update === 'function') {
compInstance.update();
}
} else {
const compInstance = new CompClass(this._getBridges(), props);
compInstance.mount(el);
this.#childComponents.set(el, compInstance);
}
} else {
console.warn(`[AvenxPage] Component '${compName}' not found in registry.`);
}
});
}
}