reblendjs
Version:
This is build using react way of handling dom but with web components
223 lines • 9.45 kB
JavaScript
/* eslint-disable @typescript-eslint/no-explicit-any */
import { REBLEND_PRIMITIVE_ELEMENT_NAME } from 'reblend-typing';
import { capitalize, isCallable, REBLEND_COMPONENT, REBLEND_WRAPPER_FOR_REACT_COMPONENT } from '../common/utils';
import { DiffUtil } from './DiffUtil';
import { NodeUtil } from './NodeUtil';
import { PropertyUtil } from './PropertyUtil';
import { Reblend } from './Reblend';
import { ReblendReactClass } from './ReblendReactClass';
const componentConfig = {
ReblendPlaceholder: true,
defaultReblendPlaceholderStyle: true
};
export const ElementUtil = class {
/**
* Creates an HTML, SVG, MathML, or XML element based on the provided tag name.
*
* @param {string} tag - The tag name of the element to create.
* @returns {HTMLElement | SVGElement | Element} The created element.
*/
static createElementWithNamespace(tag) {
const svgTags = ['svg', 'path', 'circle', 'rect', 'line', 'text', 'use', 'g', 'defs', 'clipPath', 'polygon', 'polyline', 'image', 'symbol'];
const mathMLTags = ['math', 'mi', 'mn', 'mo', 'ms', 'mtext'];
const xmlTags = ['xml', 'xmlns'];
if (svgTags.includes(tag)) {
return document.createElementNS('http://www.w3.org/2000/svg', tag);
} else if (mathMLTags.includes(tag)) {
return document.createElementNS('http://www.w3.org/1998/Math/MathML', tag);
} else if (xmlTags.includes(tag)) {
return document.createElementNS('http://www.w3.org/XML/1998/namespace', tag);
} else {
return document.createElement(tag); // Default to HTML namespace
}
}
/**
* Sets attributes on the given element, handling namespaces for XML, SVG, MathML, and XLink attributes.
*
* @param {HTMLElement | SVGElement} element - The element on which to set attributes.
* @param {Record<string, string>} attributes - A record of attribute names and values to set.
*/
static setAttributesWithNamespace(element, attributes) {
const svgAttributes = [
/* SVG attributes */
];
const mathMLAttributes = ['displaystyle', 'scriptlevel', 'mathvariant', 'mathsize', 'lspace', 'rspace'];
for (const [key, value] of Object.entries(attributes)) {
if (key.startsWith('xmlns:')) {
// Handle XML namespace attributes
element.setAttributeNS('http://www.w3.org/2000/xmlns/', key, value);
} else if (key === 'xmlns') {
// Handle default XML namespace attribute
element.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns', value);
} else if (element instanceof SVGElement && svgAttributes.includes(key)) {
// Handle SVG attributes
element.setAttributeNS('http://www.w3.org/2000/svg', key, value);
} else if (element instanceof Element && mathMLAttributes.includes(key)) {
// Handle MathML attributes
element.setAttributeNS('http://www.w3.org/1998/Math/MathML', key, value);
} else if (key.startsWith('xlink:')) {
// Handle XLink attributes
element.setAttributeNS('http://www.w3.org/1999/xlink', key, value);
} else {
// Handle standard HTML attributes
element.setAttribute(key, value);
}
}
}
/**
* Creates child nodes from the given ReblendTyping.VNode children and appends them to the container array.
* Supports nested arrays, Sets, and various node types such as Reblend, DOM Nodes, React Nodes, and primitive values.
*
* @param {ReblendTyping.VNodeChildren} children - The children to process.
* @param {(ReblendTyping.Component<P, S> | HTMLElement)[]} [containerArr=[]] - The array to store the created child nodes.
* @returns {(ReblendTyping.Component<P, S> | HTMLElement)[]} The array containing the created child nodes.
*/
static async createChildren(children, containerArr = []) {
if (!children) {
return containerArr;
}
if (!(children instanceof Set) && !Array.isArray(children)) {
children = [children];
}
for (const child of children instanceof Set ? Array.from(children) : children) {
if (isCallable(child)) {
containerArr.push(child);
} else if (Array.isArray(child)) {
ElementUtil.createChildren(child, containerArr);
} else if (child instanceof Reblend || child instanceof Node || NodeUtil.isPrimitive(child) || NodeUtil.isReactToReblendVirtualNode(child) || NodeUtil.isReblendVirtualNode(child) || NodeUtil.isStandardVirtualNode(child)) {
const domChild = DiffUtil.deepFlat(await ElementUtil.createElement(child));
domChild && containerArr.push(...domChild);
} else {
throw new TypeError('Invalid child node in children');
}
}
return containerArr;
}
/**
* Creates an element based on the provided virtual node (ReblendTyping.VNode) or primitive value.
* The created element is returned as a `BaseComponent`.
*
* @param {ReblendTyping.VNode | ReblendTyping.VNode[] | ReblendTyping.ReactNode | ReblendTyping.Primitive} vNode - The virtual node or primitive to create an element from.
* @returns {ReblendTyping.Component<P, S>[]} The created `BaseComponent` instances.
*/
static async createElement(vNode) {
if (vNode instanceof Reblend || vNode instanceof Node) {
if (!vNode.displayName) {
;
vNode.displayName = capitalize(vNode.tagName);
NodeUtil.extendPrototype(vNode, Reblend.prototype);
NodeUtil.addSymbol('ReblendNodeStandard', vNode);
vNode._constructor();
}
return [vNode];
}
if (Array.isArray(vNode)) {
return await ElementUtil.createChildren(vNode);
}
if (NodeUtil.isPrimitive(vNode)) {
return [ElementUtil.newReblendPrimitive()?.setData(vNode)];
}
const {
displayName
} = vNode;
let clazz = displayName;
const isTagStandard = typeof displayName === 'string';
const isReactNode = NodeUtil.isReactNode(displayName);
const tagName = isTagStandard ? displayName : (NodeUtil.isReactNode(clazz) ? clazz.displayName : clazz?.ELEMENT_NAME) || `Anonymous`;
isTagStandard || (clazz.ELEMENT_NAME = tagName);
if (isReactNode) {
clazz = ReblendReactClass;
clazz.ELEMENT_NAME = capitalize(`${displayName.displayName}`);
}
const element = ElementUtil.createElementWithNamespace(isTagStandard ? displayName : 'div');
NodeUtil.addSymbol(isReactNode ? 'ReactToReblendNode' : isTagStandard ? 'ReblendNodeStandard' : 'ReblendNode', element);
element.displayName = tagName;
if (isTagStandard || isReactNode) {
NodeUtil.extendPrototype(element, Reblend.prototype);
} else {
NodeUtil.extendPrototype(element, clazz.prototype);
if (clazz.config) {
Object.entries(clazz.config).forEach(([key, value]) => {
if (!componentConfig[key]) {
throw new Error(`Unsupported key \`${key}\` found in component configuration.`);
} else {
element[key] = value;
}
});
}
}
if (isReactNode) {
element.ReactClass = displayName;
NodeUtil.extendPrototype(element, ReblendReactClass.prototype);
}
element._constructor();
if (!isTagStandard) {
const isProduction = process.env.REBLEND_ENVIRONEMENT_PRODUCTION;
if (isReactNode) {
element.setAttribute(REBLEND_WRAPPER_FOR_REACT_COMPONENT, isProduction ? '' : tagName);
} else {
element.setAttribute(REBLEND_COMPONENT, isProduction ? '' : tagName);
}
}
if (isTagStandard && 'ref' in vNode.props) {
if (vNode.props.ref && !vNode.props.ref.current) {
const ref = vNode.props.ref;
const descriptor = Object.getOwnPropertyDescriptor(ref, 'current');
if (typeof ref === 'function') {
ref(element);
} else if (!descriptor || descriptor.configurable) {
Object.defineProperty(ref, 'current', {
value: element,
configurable: false,
writable: false
});
}
element.ref = ref;
}
}
if (NodeUtil.isStandard(element) || isReactNode) {
await PropertyUtil.setProps(vNode.props, element, true);
await element.populateHtmlElements();
} else {
PropertyUtil.setProps(vNode.props, element, true).finally(() => {
element.populateHtmlElements();
});
}
return [element];
}
/**
* Creates a new Reblend primitive element.
*
* @returns {ReblendTyping.Primitive} The newly created Reblend primitive element.
*/
static newReblendPrimitive() {
const text = document.createTextNode('');
NodeUtil.extendPrototype(text, Reblend.prototype);
text.displayName = REBLEND_PRIMITIVE_ELEMENT_NAME;
/**
* Sets the data of the Reblend primitive.
*
* @param {ReblendTyping.Primitive} data - The data to set.
* @returns {ReblendTyping.Primitive} The updated Reblend primitive.
*/
text.setData = function (data) {
this.reblendPrimitiveData = data;
if (this.reblendPrimitiveData !== undefined && this.reblendPrimitiveData !== null) {
const textContent = `${this.reblendPrimitiveData}`;
this.nodeValue = textContent;
} else {
this.nodeValue = '';
}
return this;
};
/**
* Gets the data of the Reblend primitive.
*
* @returns {ReblendTyping.Primitive} The data of the Reblend primitive.
*/
text.getData = function () {
return this.reblendPrimitiveData;
};
return text;
}
};