UNPKG

reblendjs

Version:

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

112 lines 4.05 kB
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { ChildrenPropsUpdateType } from 'reblend-typing'; import { NodeUtil } from './NodeUtil'; import { NodeOperationUtil } from './NodeOperationUtil'; import { REBLEND_CHILDREN_WRAPPER_FOR_REACT_COMPONENT } from '../common/utils'; /** * A static class that extends the functionality of `BaseComponent` * to provide integration with React and Reblend. */ export class ReblendReactClass { /** * Returns a React element that wraps the children of the current component for rendering in React. * It ensures that child elements are properly attached to the standard parent container. * * @returns {React.ReactElement} The React element representing the children wrapper for React. */ static async getChildrenWrapperForReact(elementChildren) { const { default: React } = await import('react'); const children = Array.from(elementChildren).map(child => { const isReactNode = NodeUtil.isReactToReblendRenderedNode(child); const isReblendNode = NodeUtil.isReblendRenderedNode(child); const isReblendPrimitiveElement = NodeUtil.isReblendPrimitiveElement(child); const tagName = isReactNode || isReblendNode ? 'div' : isReblendPrimitiveElement ? 'span' : child.displayName; return React.createElement(tagName, { [REBLEND_CHILDREN_WRAPPER_FOR_REACT_COMPONENT]: child.displayName || '', ...(child.props?.value !== undefined ? { value: child.props.value } : {}), ref: async node => { if (node) { if (!node.contains(child)) { node.appendChild(child); setTimeout(() => { if (!child.attached) { NodeOperationUtil.connected(child); } }, 0); } } else { child.remove(); } } }, null); }); return children?.length && children.length < 2 ? children.pop() : children; } html() { return this.props.children; } async checkPropsChange() { for (const type of Array.from(this.childrenPropsUpdate || []).sort()) { this.childrenPropsUpdate?.delete(type); switch (type) { case ChildrenPropsUpdateType.CHILDREN: await this.onStateChange(); break; case ChildrenPropsUpdateType.NON_CHILDREN: /* await */this.reactReblendMount(); break; default: throw new Error('Invalid props update type provided'); } } } /** * Initializes the root for React DOM rendering if it has not been created already. */ async initRoot() { const { default: ReactDOM } = await import('react-dom/client'); if (!this.reactDomCreateRoot_root || !Object.values(this.reactDomCreateRoot_root)[0]) { this.reactDomCreateRoot_root = ReactDOM.createRoot(this); } } /** * Mounts the React Reblend component by rendering it using the React DOM root and creating a portal * to the standard Reblend React container. * * @protected */ async reactReblendMount() { const { default: React } = await import('react'); if (!this.ReactClass) { return; } const children = !this.props?.children || !this.props?.children?.length ? undefined : await ReblendReactClass.getChildrenWrapperForReact(this.elementChildren); await this.initRoot(); const filtered = { ...this.props }; if (filtered.children !== undefined) { delete filtered.children; } this.reactDomCreateRoot_root?.render(React.createElement(this.ReactClass, filtered, ...(Array.isArray(children) ? children : children ? [children] : []))); } /** * Cleans up the component by resetting the React DOM root and calling the parent class's cleanup method. * */ cleanUp() { this.reactDomCreateRoot_root?.unmount(); this.reactDomCreateRoot_root = null; /* //@ts-expect-error nothing super.cleanUp() */ } }