UNPKG

reblendjs

Version:

ReblendJs uses Reactjs pradigm to build UI components, with isolated state for each components.

152 lines (133 loc) 5.01 kB
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as ReblendTyping from 'reblend-typing' import { ChildrenPropsUpdateType } from 'reblend-typing' import { REBLEND_CHILDREN_WRAPPER_FOR_REACT_COMPONENT } from '../common/utils' import { type BaseComponent } from './BaseComponent' import { connected } from './NodeOperationUtil' import { isReactToReblendRenderedNode, isReblendPrimitiveElement, isReblendRenderedNode } from './NodeUtil' // eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/no-unsafe-declaration-merging export interface ReblendReactClass extends BaseComponent {} /** * A static class that extends the functionality of `BaseComponent` * to provide integration with React and Reblend. */ // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging 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<P, S>(elementChildren: Iterable<ReblendTyping.Component<P, S>>) { let React: any try { React = require('react') } catch (err) { throw new Error('React is not available. Please ensure React is properly installed.') } const children = Array.from(elementChildren).map((child) => { const isReactNode = isReactToReblendRenderedNode(child) const isReblendNode = isReblendRenderedNode(child) const _isReblendPrimitiveElement = 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 as any)?.value !== undefined ? { value: (child.props as any).value } : {}), ref: async (node: HTMLElement) => { if (node) { if (!node.contains(child)) { node.appendChild(child) if (!child.attached) { connected(child) } } } else { child.remove() } }, }, null, ) }) return children?.length && children.length < 2 ? children.pop() : children } html() { return (this.props as any).children } async checkPropsChange() { for (const type of Array.from(this.childrenPropsUpdate || []).sort()) { this.childrenPropsUpdate?.delete(type) switch (type) { case ChildrenPropsUpdateType.CHILDREN: await (this as any).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() { try { // eslint-disable-next-line @typescript-eslint/no-var-requires const ReactDOM = require('react-dom/client') if (!this.reactDomCreateRoot_root || !Object.values(this.reactDomCreateRoot_root)[0]) { this.reactDomCreateRoot_root = ReactDOM.createRoot(this as any) } } catch (err) { throw new Error('ReactDOM is not available. Please ensure React and ReactDOM are properly installed.') } } /** * 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() { let React: any try { React = require('react') } catch (err) { throw new Error('React is not available. Please ensure React is properly installed.') } if (!this.ReactClass) { return } const children = !this.props?.children || !(this.props?.children as any)?.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(): void { this.reactDomCreateRoot_root?.unmount() this.reactDomCreateRoot_root = null as any /* //@ts-expect-error nothing super.cleanUp() */ } }