@meonode/ui
Version:
A structured approach to component composition, direct CSS-first prop styling, built-in theming, smart prop handling (including raw property pass-through), and dynamic children.
308 lines • 17.7 kB
TypeScript
import React, { type ElementType, type ReactElement, type ReactNode } from 'react';
import type { Children, FinalNodeProps, HasRequiredProps, MergedProps, NodeElement, NodeInstance, NodeProps, PropsOf, RawNodeProps, Theme } from './node.type.js';
import { type Root as ReactDOMRoot } from 'react-dom/client';
/**
* Represents a node in a React component tree with theme and styling capabilities.
* This class wraps React elements and handles:
* - Props processing and normalization
* - Theme inheritance and resolution
* - Child node processing and management
* - Style processing with theme variables
* @template E The type of React element or component this node represents
*/
export declare class BaseNode<E extends NodeElement> implements NodeInstance<E> {
/** The underlying React element or component type that this node represents */
element: E;
/** Original props passed during construction, preserved for cloning/recreation */
rawProps: RawNodeProps<E>;
/** Flag to identify BaseNode instances */
readonly isBaseNode: boolean;
/** Processed props after theme resolution, style processing, and child normalization */
private _props?;
/** DOM element used for portal rendering */
private _portalDOMElement;
/** React root instance for portal rendering */
private _portalReactRoot;
/** Hash of the current children and theme to detect changes */
private _childrenHash?;
/** Cache for normalized children */
private _normalizedChildren?;
/** Indicates whether the code is running on the server (true) or client (false) */
private static _isServer;
/**
* WeakMap cache for processed children, keyed by object/array identity for GC friendliness.
* Each entry stores the hash, processed children, and a server-side flag.
*/
private static _processedChildrenWeakCache;
/**
* Map cache for processed children, keyed by a stable string signature.
* Used for non-object cases or as a fallback. Each entry stores the processed children and a server-side flag.
*/
private static _processedChildrenMapCache;
/** Maximum number of entries in the Map cache to prevent unbounded growth */
private static readonly _MAX_PROCESSED_CHILDREN_CACHE = 1000;
/**
* Constructs a new BaseNode instance.
*
* This constructor initializes a node with a given React element or component type
* and the raw props passed to it. The props are not processed until they are
* accessed via the `props` getter, allowing for lazy evaluation.
* @param element The React element or component type this node will represent.
* @param rawProps The initial, unprocessed props for the element.
*/
constructor(element: E, rawProps?: RawNodeProps<E>);
/**
* Lazily processes and retrieves the final, normalized props for the node.
*
* The first time this getter is accessed, it triggers `_processProps` to resolve
* themes, styles, and children. Subsequent accesses return the cached result
* until the node is cloned or recreated.
* @returns The fully processed and normalized `FinalNodeProps`.
*/
get props(): FinalNodeProps;
/**
* Performs the core logic of processing raw props into their final, normalized form.
*
* This method is called by the `props` getter on its first access. It handles:
* 1. **Theme Resolution**: Selects the active theme from `theme` or `nodetheme` props.
* 2. **Prop Resolution**: Resolves theme-aware values (functions) in `rawProps` and `nativeProps.style`.
* 3. **Style Extraction**: Separates style-related props (`css`, `style`) from other DOM/component props.
* 4. **Default Style Merging**: Combines default styles with resolved style props.
* 5. **Child Processing**: Normalizes the `children` prop, propagating the theme.
* @returns The processed `FinalNodeProps` object.
* @private
*/
private _processProps;
/**
* Deeply clones processed children before returning them from cache so that each parent receives
* independent `BaseNode` instances (prevents sharing cycles and mutation bugs).
*
* - If the input is an array, each child is cloned recursively.
* - If the input is a `BaseNode`, a new instance is created with the same element and copied rawProps.
* - For other objects/primitives, the value is returned as-is (they are immutable or safe to reuse).
*
* This ensures that cached children are never shared between different parents in the React tree.
* @param processed The processed child or array of children to clone.
* @returns A deep clone of the processed children, safe for use in multiple parents.
* @private
*/
private static _cloneProcessedChildren;
/**
* Retrieves cached processed children for a given set of `children` and an optional `theme`.
*
* - Skips caching entirely when executed on the server (returns `null`).
* - Uses a **WeakMap** for identity-based caching when `children` is an object or array,
* ensuring garbage collection safety.
* - Falls back to a **Map** keyed by a stable hash of `children` and `theme`
* for value-based caching.
* - Only returns cached entries that are **not server-side**.
* @param children The child node(s) to resolve cached results for.
* @param theme The theme context that may influence child processing.
* @returns A cloned version of the cached processed children if available, otherwise `null`.
* @private
*/
private _getCachedChildren;
/**
* Caches processed children for a given set of children and theme.
* This method stores the processed NodeElement(s) in a Map keyed by a stable hash.
* The cache is bounded to avoid unbounded memory growth.
* No caching is performed on the server to avoid RSC issues.
* @param children The original children to cache.
* @param theme The theme associated with the children.
* @param processed The processed NodeElement(s) to cache.
* @private
*/
private _setCachedChildren;
/**
* Recursively processes raw children, converting them into `BaseNode` instances as needed
* and propagating the provided theme.
*
* This method ensures consistent theme handling for all children and optimizes performance
* using caching strategies: a Map for client-side and no caching for server-side.
*
* - If `children` is an array, each child is processed individually.
* - If `children` is a single node, it is processed directly.
* - The processed result is cached on the client to avoid redundant work.
* @param children The raw child or array of children to process.
* @param theme The theme to propagate to the children.
* @returns The processed children, ready for normalization and rendering.
* @private
*/
private _processChildren;
/**
* Renders a processed `NodeElement` into a `ReactNode`, applying a theme and key if necessary.
*
* This static method centralizes the logic for converting various types of processed elements
* into renderable React nodes. It handles:
* - `BaseNode` instances: Re-wraps them to apply a new key or theme.
* - React class components: Wraps them in a new `BaseNode`.
* - `NodeInstance` objects: Invokes their `render()` method.
* - React component instances: Invokes their `render()` method.
* - Functional components: Creates a React element from them.
* - Other valid `ReactNode` types (strings, numbers, etc.): Returns them as-is.
* @param processedElement The node element to render.
* @param passedTheme The theme to propagate.
* @param passedKey The React key to assign.
* @returns A renderable `ReactNode`.
* @private
* @static
*/
static _renderProcessedNode(processedElement: NodeElement, passedTheme: Theme | undefined, passedKey?: string): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | ReactElement<unknown, string | React.JSXElementConstructor<any>> | React.ReactPortal | null | undefined> | ReactElement<any, string | React.JSXElementConstructor<any>> | null | undefined;
/**
* Renders the output of a function-as-a-child, ensuring theme propagation.
*
* This method is designed to handle "render prop" style children (`() => ReactNode`).
* It invokes the function, processes its result, and ensures the parent's theme is
* correctly passed down to any `BaseNode` instances returned by the function.
* @param props The properties for the function renderer.
* @param props.render The function to execute to get the child content.
* @param props.passedTheme The theme to propagate to the rendered child.
* @param props.processRawNode A reference to the `_processRawNode` method for recursive processing.
* @returns The rendered `ReactNode`.
* @private
*/
private _functionRenderer;
/**
* Generates a stable key for a node, especially for elements within an array.
*
* If an `existingKey` is provided, it is returned. Otherwise, a key is generated
* based on the element's type name and its index within a list of siblings.
* This helps prevent re-rendering issues in React when dealing with dynamic lists.
* @param options The options for key generation.
* @param options.nodeIndex The index of the node in an array of children.
* @param options.element The element for which to generate a key.
* @param options.existingKey An existing key, if one was already provided.
* @param options.children The children of the node, used to add complexity to the key.
* @returns A React key, or `undefined` if no key could be generated.
* @private
*/
private _generateKey;
/**
* Processes a single raw node, recursively converting it into a `BaseNode` or other renderable type.
*
* This is a central method for normalizing children. It handles various types of input:
* - **`BaseNode` instances**: Re-creates them to ensure the correct theme and key are applied.
* - **Primitives**: Returns strings, numbers, booleans, null, and undefined as-is.
* - **Functions (Render Props)**: Wraps them in a `BaseNode` that uses `_functionRenderer` to delay execution.
* - **Valid React Elements**: Converts them into `BaseNode` instances, extracting props and propagating the theme.
* - **React Component Types**: Wraps them in a `BaseNode` with the parent theme.
* - **React Component Instances**: Renders them and processes the output recursively.
*
* It also generates a stable key for elements within an array if one is not provided.
* @param rawNode The raw child node to process.
* @param parentTheme The theme inherited from the parent.
* @param nodeIndex The index of the child if it is in an array, used for key generation.
* @returns A processed `NodeElement` (typically a `BaseNode` instance or a primitive).
* @private
*/
private _processRawNode;
/**
* Normalizes a processed child node into a final, renderable `ReactNode`.
*
* This method is called during the `render` phase. It takes a child that has already
* been processed by `_processChildren` and prepares it for `React.createElement`.
*
* - For `BaseNode` instances, it calls their `render()` method, ensuring the theme is consistent.
* - It validates that other children are valid React element types.
* - Primitives and other valid nodes are returned as-is.
* @param child The processed child node to normalize.
* @returns A renderable `ReactNode`.
* @throws {Error} If the child is not a valid React element type.
* @private
*/
private _normalizeChild;
/**
* Renders the `BaseNode` into a `ReactElement`.
*
* This method is the final step in the rendering pipeline. It constructs a React element
* by:
* 1. Validating that the node's `element` type is renderable.
* 2. Normalizing processed children into `ReactNode`s using `_normalizeChild`.
* 3. Caching normalized children to avoid re-processing on subsequent renders.
* 4. Assembling the final props, including `key`, `style`, and other attributes.
* 5. If the element has a `css` prop, it may be wrapped in a `StyledRenderer` to handle
* CSS-in-JS styling.
* 6. Finally, calling `React.createElement` with the element, props, and children.
* @returns The rendered `ReactElement`.
* @throws {Error} If the node's `element` is not a valid React element type.
*/
render(): ReactElement<FinalNodeProps>;
/**
* Ensures the necessary DOM elements for portal rendering are created and attached.
*
* On the client-side, this method checks for or creates a `div` element appended
* to the `document.body` and initializes a React root on it. This setup is
* required for the `toPortal` method to function. It is idempotent and safe
* to call multiple times.
* @returns `true` if the portal infrastructure is ready, `false` if on the server.
* @private
*/
private _ensurePortalInfrastructure;
/**
* Renders the node into a React Portal.
*
* This method mounts the node's rendered content into a separate DOM tree
* attached to the `document.body`. It's useful for rendering components like
* modals, tooltips, or notifications that need to appear above other UI elements.
*
* The returned object includes an `unmount` function to clean up the portal.
* @returns A `ReactDOMRoot` instance for managing the portal, or `null` if
* called in a server-side environment. The returned instance is enhanced
* with a custom `unmount` method that also cleans up the associated DOM element.
*/
toPortal(): ReactDOMRoot | null;
}
/**
* Factory function to create a `BaseNode` instance.
* @template AdditionalProps Additional props to merge with node props.
* @template E The React element or component type.
* @param element The React element or component type to wrap.
* @param props The props for the node (optional).
* @param additionalProps Additional props to merge into the node (optional).
* @returns A new `BaseNode` instance as a `NodeInstance<E>`.
*/
export declare function Node<AdditionalProps extends Record<string, any>, E extends NodeElement>(element: E, props?: MergedProps<E, AdditionalProps>, additionalProps?: AdditionalProps): NodeInstance<E>;
/**
* Creates a curried node factory for a given React element or component type.
*
* Returns a function that, when called with props, produces a `NodeInstance<E>`.
* Useful for creating reusable node factories for specific components or element types.
* @template AdditionalInitialProps Additional initial props to merge with node props.
* @template E The React element or component type.
* @param element The React element or component type to wrap.
* @param initialProps Initial props to apply to every node instance.
* @returns A function that takes node props and returns a `NodeInstance<E>`.
* @example
* const ButtonNode = createNode('button', { type: 'button' });
* const myButton = ButtonNode({ children: 'Click me', style: { color: 'red' } });
*/
export declare function createNode<AdditionalInitialProps extends Record<string, any>, E extends NodeElement>(element: E, initialProps?: MergedProps<E, AdditionalInitialProps>): HasRequiredProps<PropsOf<E>> extends true ? (<AdditionalProps extends Record<string, any> = Record<string, any>>(props: MergedProps<E, AdditionalProps>) => NodeInstance<E>) & {
element: E;
} : (<AdditionalProps extends Record<string, any> = Record<string, any>>(props?: MergedProps<E, AdditionalProps>) => NodeInstance<E>) & {
element: E;
};
/**
* Creates a node factory function where the first argument is `children` and the second is `props`.
*
* Useful for ergonomic creation of nodes where children are the primary concern,
* such as layout or container components.
*
* The returned function takes `children` as the first argument and `props` (excluding `children`) as the second.
* It merges any `initialProps` provided at factory creation, then creates a `BaseNode` instance.
*
* Type parameters:
* - `AdditionalInitialProps`: Extra props to merge with node props.
* - `E`: The React element or component type.
* @param element The React element or component type to wrap.
* @param initialProps Initial props to apply to every node instance (excluding `children`).
* @returns A function that takes `children` and `props`, returning a `NodeInstance<E>`.
* @example
* const Text = createChildrenFirstNode('p');
* const myDiv = Text('Hello', { className: 'text-lg' });
*/
export declare function createChildrenFirstNode<AdditionalInitialProps extends Record<string, any>, E extends ElementType>(element: E, initialProps?: Omit<NodeProps<E>, keyof AdditionalInitialProps | 'children'> & AdditionalInitialProps): HasRequiredProps<PropsOf<E>> extends true ? (<AdditionalProps extends Record<string, any> = Record<string, any>>(children: Children, props: Omit<MergedProps<E, AdditionalProps>, 'children'>) => NodeInstance<E>) & {
element: E;
} : (<AdditionalProps extends Record<string, any> = Record<string, any>>(children?: Children, props?: Omit<MergedProps<E, AdditionalProps>, 'children'>) => NodeInstance<E>) & {
element: E;
};
//# sourceMappingURL=core.node.d.ts.map