UNPKG

reblendjs

Version:

This is build using react way of handling dom but with web components

223 lines 9.45 kB
/* 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; } };