import { Fragment, cloneElement, createElement, forwardRef, isValidElement, // Types ElementType, ReactElement, } from 'react' import { Props, XOR, __, Expand } from '../types' import { match } from './match' export enum Features { /** No features at all */ None = 0, /** * When used, this will allow us to use one of the render strategies. * * **The render strategies are:** * - **Unmount** _(Will unmount the component.)_ * - **Hidden** _(Will hide the component using the [hidden] attribute.)_ */ RenderStrategy = 1, /** * When used, this will allow the user of our component to be in control. This can be used when * you want to transition based on some state. */ Static = 2, } export enum RenderStrategy { Unmount, Hidden, } type PropsForFeature = { [P in TPassedInFeatures]: P extends TForFeature ? TProps : __ }[TPassedInFeatures] export type PropsForFeatures = XOR< PropsForFeature, PropsForFeature > export function render( props: Expand & PropsForFeatures>, propsBag: TBag, defaultTag: ElementType, features?: TFeature, visible: boolean = true ) { // Visible always render if (visible) return _render(props, propsBag, defaultTag) let featureFlags = features ?? Features.None if (featureFlags & Features.Static) { let { static: isStatic = false, ...rest } = props as PropsForFeatures // When the `static` prop is passed as `true`, then the user is in control, thus we don't care about anything else if (isStatic) return _render(rest, propsBag, defaultTag) } if (featureFlags & Features.RenderStrategy) { let { unmount = true, ...rest } = props as PropsForFeatures let strategy = unmount ? RenderStrategy.Unmount : RenderStrategy.Hidden return match(strategy, { [RenderStrategy.Unmount]() { return null }, [RenderStrategy.Hidden]() { return _render( { ...rest, ...{ hidden: true, style: { display: 'none' } } }, propsBag, defaultTag ) }, }) } // No features enabled, just render return _render(props, propsBag, defaultTag) } function _render( props: Expand & { ref?: unknown }>, bag: TBag, tag: ElementType ) { let { as: Component = tag, children, refName = 'ref', ...passThroughProps } = omit(props, [ 'unmount', 'static', ]) // This allows us to use `` let refRelatedProps = props.ref !== undefined ? { [refName]: props.ref } : {} let resolvedChildren = (typeof children === 'function' ? children(bag) : children) as | ReactElement | ReactElement[] if (Component === Fragment) { if (Object.keys(passThroughProps).length > 0) { if (Array.isArray(resolvedChildren) && resolvedChildren.length > 1) { let err = new Error('You should only render 1 child') if (Error.captureStackTrace) Error.captureStackTrace(err, _render) throw err } if (!isValidElement(resolvedChildren)) { let err = new Error( `You should render an element as a child. Did you forget the as="..." prop?` ) if (Error.captureStackTrace) Error.captureStackTrace(err, _render) throw err } return cloneElement( resolvedChildren, Object.assign( {}, // Filter out undefined values so that they don't override the existing values mergeEventFunctions(compact(omit(passThroughProps, ['ref'])), resolvedChildren.props, [ 'onClick', ]), refRelatedProps ) ) } } return createElement( Component, Object.assign({}, omit(passThroughProps, ['ref']), Component !== Fragment && refRelatedProps), resolvedChildren ) } /** * We can use this function for the following useCase: * *