import {ErrorCode, ExceptionIdMap} from '../../exception/exceptionDesc.js';
import {Component_A} from './as-component.js';
import {Popup_A} from '../as-popup.js';
/**
* @typedef TComponents.ContainerProps
* @prop {TComponents.Component_A | HTMLElement | TComponents.Component_A[] | HTMLElement[]} [children]
* @prop {boolean} [box] If true, the container will have a box around it
* @prop {string} [width] Width of the container. Default value is “auto”.
* @prop {string} [height] Height of the container. Default value is “auto”.
* @prop {string} [classNames] Additional class names to be added to the container.
* It can be a string, e.g. 'flex-row items-start justify-start',
* or an array of strings, e.g. ['flex-row', 'items-start', 'justify-start']
* @prop {string} [id] Name of container (optional). For instance, if container is part of a layout, the name of the prop
* corresponding to the container shall be given further to the container. The name will be then exposed in the DOM element
* as data-name attribute.
*/
/**
* @ignore
*/
const logModule = 'as-container';
/**
* @description Container that can hold other components or HTML elements. It can be used to create row or column containers.
* @class TComponents.Container_A
* @extends TComponents.Component_A
* @memberof TComponents
* @param {HTMLElement} parent - HTML element that is going to be the parent of the component
* @param {TComponents.ContainerProps} [props]
* @example
* const container = new TComponents.Container_A(document.body, {
* children: [new TComponents.Button(null, {})],
* box: true,
* width: '100%',
* height: '100%',
* classNames: ['flex-row', 'items-start', 'justify-start'],
* id: 'my-container'
* });
*/
export class Container_A extends Component_A {
constructor(parent, props = {}) {
super(parent, props);
this._children = new Map();
this.initPropsDep('children');
}
/**
* @returns {boolean}
*/
get enabled() {
return this._enabled;
}
/**
* @description Updates the enabled state of the container component.
* @member {boolean} TComponents.Container_A#enabled
* @instance
* @param {boolean} t - `true` to enable the component, `false` to disable it.
* @example
* const container = new TComponents.Container_A(document.body, {});
* container.enabled = true;
*/
set enabled(t) {
this._enabled = t === true ? true : false;
this.container.classList.toggle('t-component__container-disabled', !this._enabled);
}
/**
* @description Returns the default values of class properties (excluding parent properties).
* @member TComponents.Container_A#defaultProps
* @method
* @protected
* @returns {TComponents.ContainerProps}
*/
defaultProps() {
return {
children: [],
row: false,
box: false,
width: '100%',
height: '100%',
classNames: ['flex-row', 'justify-stretch'],
id: '',
};
}
/**
* @description Initializes the component. This method is called after the component is created and added to the DOM.
* @member TComponents.Container_A#onInit
* @method
* @async
* @throws {Error} Throws an error if a child element cannot be found in the DOM.
* @returns {Promise<void>}
*/
async onInit() {
this._children.clear();
const childrenArray = Array.isArray(this._props.children)
? this._props.children
: [this._props.children ? this._props.children : []];
childrenArray.forEach((child, idx) => {
if (typeof child === 'string') {
// chekc if child has # and remove it
const elementId = child.replace(/^#/, '');
child = elementId.startsWith('.') ? document.querySelector(elementId) : document.getElementById(elementId);
if (!child) {
Logger.e(
logModule,
ErrorCode.FailedToFindChildElement,
`Container_A: Could not find element with selector/id: ${elementId} in the DOM.`,
);
Popup_A.danger(
`${ExceptionIdMap.FailedToFindChildElement}-${Component_A.t(`framework:${ExceptionIdMap.FailedToFindChildElement}.title`, {elementId: elementId})}`,
Component_A.t(`framework:${ExceptionIdMap.FailedToFindChildElement}.causes`),
);
}
if (!child.id) {
child.id = this._childId(idx);
}
this._children.set(child, child.id);
} else if (Component_A.isTComponent(child)) {
this._children.set(child, child.compId);
} else if (Component_A._isHTMLElement(child)) {
if (!child.id) {
child.id = this._childId(idx);
}
this._children.set(child, child.id);
} else {
Logger.e(
logModule,
ErrorCode.InvalidChildElement,
`Container_A: Unexpected type of child detected: ${typeof child}. Expected TComponent or HTMLElement.`,
);
Popup_A.danger(
`${ExceptionIdMap.InvalidChildElement}-${Component_A.t(`framework:${ExceptionIdMap.InvalidChildElement}.title`, {type: typeof child})}`,
Component_A.t(`framework:${ExceptionIdMap.InvalidChildElement}.causes`),
);
}
});
this._props.children = [...this._children.values()];
// Ensure the component ID is set, generating one if not provided.
this.compId = typeof this._props.id === 'string' ? this._props.id : this.compId;
}
/**
* @description Maps the components in the container.
* @member TComponents.Container_A#mapComponents
* @method
* @returns {object} An object containing the mapped components.
*/
mapComponents() {
return {children: [...this._children.keys()]};
}
/**
* @description Renders the container and its children.
* @member TComponents.Container_A#onRender
* @method
* @returns {void}
*/
onRender() {
// Always ensure a component ID is set, generating one if not provided.
this.container.id = typeof this._props.id === 'string' ? this._props.id : this.compId;
// this.container.dataset.containerId = this._props.id ? this._props.id : this.compId;
this.container.dataset.tcContainer = 'true';
this._children.forEach((id, child) => {
if (Component_A.isTComponent(child)) {
child.attachToElement(this.container);
} else {
if (child.parentNode) {
child.parentNode.removeChild(child);
}
this.container.append(child);
}
});
if (this._props.box) this.cssBox();
this.cssAddClass('this', [
't-component__container',
'flex-row',
// `${this._props.row ? 'flex-row' : 'flex-row'}`,
]);
if (this._props.classNames) this.cssAddClass('this', this._props.classNames ? this._props.classNames : 'flex-row');
this.container.style.width = this._props.width;
this.container.style.height = this._props.height;
this.container.style.setProperty('padding', '0px', 'important');
this.container.style.setProperty('margin', '0px', 'important');
}
/**
* @description Generates an ID for a child element based on its index.
* @member TComponents.Container_A~_childId
* @private
* @param {number} idx Index of the child element.
* @returns {string} Generated ID for the child element.
*/
_childId(idx) {
return `${this.compId}__child-${idx}`;
}
/**
* @description Generates a selector for a child element based on its index.
* @member TComponents.Container_A~_childSelector
* @method
* @private
* @param {number} idx Index of the child element.
* @returns {string} Generated selector for the child element.
*/
_childSelector(idx) {
return `child-${idx}`;
}
/**
* @description Add/remove a class to a specific child element. The index of the child element is the same as the index of the child element in the children array.
* @member TComponents.Container_A#cssItem
* @method
* @param {number} index Index of the child element to be styled.
* @param {string} className Name of the class to be added (removed).
* @param {boolean} remove If true, the class will be removed.
* @returns {void}
*/
cssItem(index, className, remove = false) {
if (remove) this.cssRemoveClass(`.${this._childId(index)}`, className);
else this.cssAddClass(`.${this._childId(index)}`, className);
}
/**
* @description Add/remove a class to all child elements.
* @member TComponents.Container_A#cssItems
* @method
* @param {string} className Name of the class to be added (removed).
* @param {boolean} remove If true, the class will be removed.
* @returns {void}
*/
cssItems(className, remove = false) {
if (remove) this.cssRemoveClass('.child__container', className);
else this.cssAddClass('.child__container', className, true);
}
}
/**
* @description Add css properties to the component
* @member TComponents.Container_A.loadCssClassFromString
* @method
* @static
* @param {string} css - The css string to be loaded into style tag
* @returns {void}
* @example
* Container_A.loadCssClassFromString(`
* .t-component__container {
* max-width: inherit;
* max-height: inherit;
* width: 100% !important;
* height: 100% !important;
* }
*
* .t-component__container-disabled {
* cursor: not-allowed;
* opacity: 0.7;
* }
*
* .t-component__container-disabled > * {
* pointer-events: none;
* }
* `);
*/
Container_A.loadCssClassFromString(/*css*/ `
.t-component__container {
max-width: inherit;
max-height: inherit;
width: 100% !important;
height: 100% !important;
}
.t-component__container-disabled {
cursor: not-allowed;
opacity: 0.7;
}
.t-component__container-disabled > * {
pointer-events: none;
}
`);