UNPKG

@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.

187 lines 9.43 kB
import React, { type CSSProperties, type ReactNode, type JSX, type ElementType, type ComponentType, type JSXElementConstructor, type Component, type ExoticComponent, type ReactElement } from 'react'; import type { Root as ReactDOMRoot } from 'react-dom/client'; import type { CSSInterpolation } from '@emotion/serialize'; import type { NO_STYLE_TAGS } from './constants/common.const.js'; // --- Utility Types --- // Utility to get keys of required properties in a type T. type RequiredKeys<T> = { [K in keyof T]-?: object extends Pick<T, K> ? never : K; }[keyof T]; // Utility to check if a type T has any required properties. export type HasRequiredProps<T> = RequiredKeys<T> extends never ? false : true; /** Basic React attributes, currently only includes 'key' */ export interface ReactAttributes { key?: string; } /** * Excludes array types from ReactNode, ensuring a single, non-array React element or primitive. */ export type NonArrayReactNode = Exclude<ReactNode, ReactNode[]>; /** * Defines the various types that can represent a "node" in the Meonode system. * This includes React elements, components, promises resolving to React nodes, and NodeInstance objects. */ export type NodeElement = ExoticComponent<any> | NonArrayReactNode | Promise<Awaited<NonArrayReactNode>> | Component<any, any, any> | ElementType | ComponentType<any> | NodeInstance<any> | ((props?: any) => NonArrayReactNode | Promise<Awaited<NonArrayReactNode>> | Component<any, any, any> | NodeInstance<any> | ComponentNode); /** A single NodeElement or an array of NodeElements */ export type Children = NodeElement | NodeElement[]; /** * Forward declaration of the BaseNode interface to avoid circular dependencies. * Defines the core structure and capabilities of a BaseNode instance. * @template T - The type of React element/component that this node represents */ export interface NodeInstance<T extends NodeElement = NodeElement> { /** The underlying React element or component type that this node will render */ readonly element: T; /** Original props passed during node construction, preserved for cloning/recreation */ readonly rawProps: RawNodeProps<T>; readonly isBaseNode: true; /** Converts this node instance into a renderable React element/tree */ render(): ReactElement; /** Creates Portal-compatible React elements for rendering outside of the DOM tree */ toPortal(): ReactDOMRoot | null; } /** * Infers the props type for a given NodeElement. * - For intrinsic JSX elements (e.g., 'div', 'span'), returns the corresponding JSX.IntrinsicElements props. * - For React components (function or class), infers the props from the component type. * - For objects with a `props` property, infers the type from that property. * - Otherwise, resolves to `never`. * @template E - The NodeElement type to extract props from. */ export type PropsOf<E extends NodeElement> = E extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[E] : E extends JSXElementConstructor<infer P> ? P : E extends { props: infer Q; } ? Q : never; /** * Theme configuration object that can be passed through the node tree. * Supports nested theme properties for complex styling systems: * - Simple values (strings, numbers) * - Nested theme objects with unlimited depth * - Common CSS values and units * - Custom theme variables and tokens * Used for consistent styling and dynamic theme application. */ export type Theme = Partial<{ [key: string]: string | number | boolean | null | undefined | any | Theme | Record<string, Theme | string | number | boolean | null | undefined | any>; }>; /** * Internal props type used by BaseNode instances after initial processing. * Ensures consistent prop shape throughout the node's lifecycle: * - Normalizes style properties into a CSSProperties object * - Processes children into known concrete types * - Handles theme context propagation * @template E - The element type these props apply to */ export type FinalNodeProps = ReactAttributes & Partial<{ nativeProps: Omit<Omit<PropsOf<NodeElement>, 'children'>, 'style'>; key: React.Key | any | null | undefined; ref: any | React.Ref<unknown> | undefined; style: any; css: any; children: Children; theme: Partial<{ [p: string]: any; }> | any | undefined; nodetheme: Theme; }>; /** * Helper type to determine if the props P have a 'style' property * that is compatible with CSSProperties. * @template P - The props object of a component (e.g., PropsOf<E>) */ export type HasCSSCompatibleStyleProp<P> = P extends { style?: infer S; } // Does P have a 'style' prop (even optional)? ? S extends CSSProperties | undefined // Is the type of that 'style' prop (S) assignable to CSSProperties or undefined? ? true // Yes, it's CSS compatible : false // No, 'style' exists but is not CSSProperties (e.g., style: string) : false; // No, P does not have a 'style' prop at all /** List of HTML tags that should not receive style props */ export type NoStyleTags = (typeof NO_STYLE_TAGS)[number]; /** * Helper type to determine if the element E is a tag that should not receive style props. * Uses the NO_STYLE_TAGS constant to check against known tags. * @template E - The element type (e.g., 'div', 'span', 'script') */ export type HasNoStyleProp<E extends NodeElement> = E extends NoStyleTags ? true : false; /** * Public API for node creation props, providing a flexible and type-safe interface: * - Preserves original component props while allowing direct style properties (conditionally) * - Supports both single and array children * - Enables theme customization * - Maintains React's key prop for reconciliation * @template E - The element type these props apply to */ export type NodeProps<E extends NodeElement> = Omit<PropsOf<E>, keyof CSSProperties | 'children' | 'style' | 'theme' | 'props'> & ReactAttributes & (HasCSSCompatibleStyleProp<PropsOf<E>> extends true ? CSSProperties : object) & (HasNoStyleProp<E> extends true ? Partial<{ css: CSSInterpolation; }> : object) & Partial<{ props: Partial<Omit<PropsOf<E>, 'children'>>; children: Children; theme: Theme; }>; /** * BaseNode's internal props type, extending NodeProps: * - Makes all properties optional for flexible node creation * - Adds nodetheme for theme context handling * - Used for both initial construction and internal state * @template E - The element type these props apply to */ export type RawNodeProps<E extends NodeElement> = Partial<NodeProps<E>> & { nodetheme?: Theme; }; /** * Props interface for the internal FunctionRenderer component. * Handles dynamic function children within React's component lifecycle: * - Ensures proper timing of function evaluation * - Maintains theme context for rendered content * - Enables hook usage in function children */ export interface FunctionRendererProps<E extends NodeElement> { /** Function that returns the child content to render */ render: (props?: NodeProps<E>) => ReactNode | Promise<Awaited<ReactNode>> | React.Component | NodeInstance<E>; /** Theme context to be applied to the rendered content */ passedTheme?: Theme; processRawNode: (node: NodeElement, parentTheme?: Theme, childIndex?: number) => NodeElement; } export type ComponentNode = (NodeInstance<any> | ReactNode) | (() => NodeInstance<any> | ReactNode); /** * Base props interface for components rendered inside a portal. * @property children - The content to render within the portal. Accepts a single NodeElement or an array of NodeElements. * @property portal - An object providing portal lifecycle control methods. * @property unmount - Function to unmount and clean up the portal instance. */ export interface BasePortalProps { /** Content to render within the portal */ children?: Children; /** Portal control object containing lifecycle methods */ portal: { /** Unmounts and cleans up the portal */ unmount: () => void; }; } /** * Props type for components rendered through the Portal HOC. * Extends the component's own props with portal-specific functionality. * @template T The component's own prop types */ export type PortalProps<T extends BasePortalProps | Record<string, any>> = T & BasePortalProps; /** * Function type for creating portal instances. * Allows passing providers through props at portal creation time. * @template P The portal content component's prop types */ export type PortalLauncher<P extends BasePortalProps | Record<string, any>> = P extends BasePortalProps ? (props?: { /** Optional provider components to wrap the portal content */ provider?: NodeInstance<any>; }) => ReactDOMRoot | null : (props: P & { /** Optional provider components to wrap the portal content */ provider?: NodeInstance<any>; } & Omit<PortalProps<P>, 'portal'>) => ReactDOMRoot | null; /** * Merges `NodeProps<E>` with additional custom props, giving precedence to `AdditionalProps`. * Useful for extending node props with extra properties, while overriding any overlapping keys. * @template E - The node element type * @template AdditionalProps - The additional props to merge in */ export type MergedProps<E extends NodeElement, AdditionalProps extends Record<string, any>> = Omit<NodeProps<E> & AdditionalProps, keyof AdditionalProps> & AdditionalProps; export {}; //# sourceMappingURL=node.type.d.ts.map