import API from '../../api/ecosystem-base.js';
import {Component_A} from './as-component.js';
/**
* @typedef {Map<Component_A, string>} TComponents.ViewChildProps
* @description
* A map of view child components:
* - **Key**: an instance of `Component_A`
* - **Value**: the name of that child component as a string
* @memberof TComponents
*/
/**
* @ignore
*/
const logModule = 'as-view';
/**
* @typedef {object} TComponents.ViewProps
* @property {any} [id] - The ID of the view.
* @property {string} [name=''] - Name of the view.
* @property {string} [icon=''] - Icon associated with the view.
* @property {string | HTMLElement | null} [content=null] - Content to display in the view.
* The value should be a string when passed via props, but null or an HTMLElement when used in component_a's views property.
* @property {TComponents.Component_A[]} [children=[]] - Child components.
* @memberof TComponents
*/
/**
* @description Base view component extending Component_A.
* Manages activation state, identification, icon, content, and child component mapping.
* @class TComponents.View_A
* @extends TComponents.Component_A
* @memberof TComponents
* @param {HTMLElement} parent - The parent DOM element to which this view will attach.
* @param {TComponents.ViewProps} [props={}] - Initial properties for the view.
* @example
* const view = new TComponents.View_A(document.body, {
* name: 'My View',
* icon: 'view-icon',
* content: 'This is the view content',
* children: [new TComponents.Component_A()]
* });
*/
export class View_A extends Component_A {
constructor(parent = null, props = {}) {
super(parent, props);
/** @type {TComponents.ViewProps} */
this._props;
/** @type {TComponents.ViewChildProps} */
this._children = new Map();
this._newChildren = new Map();
}
/**
* @returns {string}
*/
get icon() {
return this._props.icon;
}
/**
* @description Set the view's icon.
* @member {string} TComponents.View_A#icon
* @instance
* @param {string} value - Icon name or URL.
* @example
* const view = new TComponents.View_A(document.body, {
* name: 'My View',
* icon: 'view-icon',
* content: 'This is the view content',
* children: [new TComponents.Component_A()]
* });
* view.icon = 'new-icon';
*/
set icon(value) {
this.commitProps({icon: value});
}
/**
* @returns {HTMLElement|string|null}
*/
get content() {
return this._props.content;
}
/**
* @description List of child components in the view.
* The obtained value should be treated as a read-only property and should not be used for manipulation.
* @member {TComponents.Component_A[]} TComponents.View_A#children
* @instance
*/
get children() {
return Array.from(new Set([...this._props.children, ...this._newChildren.keys()]));
}
/**
* @description Default property values for the view.
* Overrides parent defaults; not including inherited props.
* @member TComponents.View_A#defaultProps
* @method
* @protected
* @returns {TComponents.ViewProps}
*/
defaultProps() {
return {
name: '',
icon: '',
content: '',
children: [],
};
}
/**
* @description Lifecycle method called when the component is initialized.
* Override to perform async setup logic.
* @member TComponents.View_A#onInit
* @method
* @async
* @returns {Promise<void>}
*/
async onInit() {
this._children.clear();
const childrenArray = Object.values(this._props.children || []);
childrenArray.forEach((child, _) => {
if (Component_A.isTComponent(child)) {
this._children.set(child, child.compId);
}
});
// Make sure the component container becomes the content of the view,
// since the markup function returns nothing.
if (Component_A._isHTMLElement(this._props.content)) {
this._props.content.classList.add('t-component__container', 'flex-row', 'justify-stretch');
this._props.content.dataset.nodeId = this._props.content.id;
}
}
/**
* @description Map and return child components when the view is active.
* Iterates over `child` prop and returns Tcomponents or global instances.
* @member TComponents.View_A#mapComponents
* @method
* @returns {TComponents.ViewChildProps|object} Mapped child components by key.
*/
mapComponents() {
// This flag indicates whether the view has been rendered.
// For view components that have already been rendered, we shouldn't render them again or trigger re-initialization.
// We shallow-cloned the HTMLElement instances during initialization via this.find, and stored those elements in this._props.
if (this.initialized) {
return {};
}
const allChildren = Array.from(new Set([...this._children.keys(), ...this._newChildren.keys()]));
return {children: allChildren};
}
/**
* @description Lifecycle method called when rendering the component.
* Override to implement custom render behavior.
* @member TComponents.View_A#onRender
* @method
* @returns {void}
*/
onRender() {
// The `render()` method resets this.container.innerHTML,
// which would sever the node references cached by `this.find()`,
// so we need to preserve those elements.
if (this.initialized) {
// Assign the actual value to the child.
this.child = {children: Array.from(new Set([...this._children.keys(), ...this._newChildren.keys()]))};
}
const target = this._props.content;
if (!Component_A._isHTMLElement(target)) return;
// Attach all elements.
this.child.children.forEach((child) => {
if (Component_A.isTComponent(child)) {
child.attachToElement(target);
} else {
if (child.parentNode) {
child.parentNode.removeChild(child);
}
target.append(child);
}
});
}
/**
* @description Appends a new child (component or HTMLElement) to the container.
* @member TComponents.View_A#appendChild
* @method
* @param {TComponents.Component_A|HTMLElement} t - The child instance to append. Can be a TComponent or a DOM element.
* @param {string} [name=''] - Optional identifier for the child. If omitted and `t` is a TComponent, `t.compId` is used.
* @returns {void}
* @example
* const view = new TComponents.View_A(null, {});
* const child = new TComponents.Button(null, {});
* view.appendChild(child, 'test');
*/
appendChild(t, name = '') {
const target = this._props.content;
if (Component_A.isTComponent(t)) {
this._newChildren.set(t, name || t.compId);
const allChildren = Array.from(new Set([...this._children.keys(), ...this._newChildren.keys()]));
this.child.children = allChildren;
t.render();
t.attachToElement(target);
} else if (Component_A._isHTMLElement(t)) {
if (!target.contains(t)) {
this._newChildren.set(t, name);
const allChildren = Array.from(new Set([...this._children.keys(), ...this._newChildren.keys()]));
this.child.children = allChildren;
target.appendChild(t);
}
}
}
/**
* @description Removes a previously added child from the internal `_newChildren` registry.
* @member TComponents.View_A#removeChild
* @method
* @param {TComponents.Component_A|HTMLElement} value - The child instance to remove. Must match the reference used in `appendChild`.
* @returns {void}
* @example
* const view = new TComponents.View_A(null, {});
* const child = new TComponents.Button(null, {});
* view.appendChild(child, 'test');
* view.removeChild(child);
*/
removeChild(value) {
const destroyOrRemove = (child) => {
if (Component_A.isTComponent(child)) {
child.destroy();
} else if (child.remove) {
child.remove();
}
};
if (typeof value === 'string') {
for (const [key, val] of this._newChildren.entries()) {
if (val === value) {
destroyOrRemove(key);
this._newChildren.delete(key);
}
}
} else {
destroyOrRemove(value);
this._newChildren.delete(value);
}
const allChildren = [...this._children.keys(), ...this._newChildren.keys()];
this.child.children = allChildren;
}
/**
* @description Creates a default set of properties for a new view.
* @member TComponents.View_A.createViewProps
* @method
* @static
* @returns {TComponents.ViewProps} A fresh view props object.
*/
static createViewProps() {
return {
name: 'Item 0',
content: `view_${API.generateUUID()}`,
icon: '',
children: [],
};
}
}