import { Ref , createRef , ComponentChildren , createElement , RefObject , createContext , Context } from '../vdom' ; import { setRef , BaseComponent } from '../vdom-util' ; import { isPropsEqual } from '../util/object' ; import { parseClassNames , ClassNamesInput } from '../util/html' ; export type MountArg < ContentArg > = ContentArg & { el : HTMLElement ; } ; export type DidMountHandler < TheMountArg extends { el : HTMLElement ; } > = ( mountArg : TheMountArg ) => void ; export type WillUnmountHandler < TheMountArg extends { el : HTMLElement ; } > = ( mountArg : TheMountArg ) => void ; export interface RenderHookProps < ContentArg > { hookProps : ContentArg ; classNames : ClassNamesGenerator < ContentArg > ; content : CustomContentGenerator < ContentArg > ; defaultContent ? : DefaultContentGenerator < ContentArg > ; didMount : DidMountHandler < MountArg < ContentArg > > ; willUnmount : WillUnmountHandler < MountArg < ContentArg > > ; children : RenderHookPropsChildren ; elRef ? : Ref < any > ; } export type RenderHookPropsChildren = ( rootElRef : Ref < any > , classNames : string [ ] , innerElRef : Ref < any > , innerContent : ComponentChildren ) => ComponentChildren ; export interface ContentTypeHandlers { [ contentKey ] : ( ) => ( { render : ( el : HTMLElement , contentVal : any ) => void ; destroy ? : ( ) => void ; } ) ; } export class RenderHook < HookProps > extends BaseComponent < RenderHookProps < HookProps > > { private rootElRef = createRef ( ) ; render ( ) { let { props } = this ; let { hookProps } = props ; return ( < MountHook hookProps = { hookProps } didMount = { props . didMount } willUnmount = { props . willUnmount } elRef = { this . handleRootEl } > { ( rootElRef ) => ( < ContentHook hookProps = { hookProps } content = { props . content } defaultContent = { props . defaultContent } backupElRef = { this . rootElRef } > { ( innerElRef , innerContent ) => props . children ( rootElRef , normalizeClassNames ( props . classNames , hookProps ) , innerElRef , innerContent ) } < /ContentHook>)} < /MountHook>); } handleRootEl = ( el ) => { setRef ( this . rootElRef , el ) ; if ( this . props . elRef ) { setRef ( this . props . elRef , el ) ; } } ; } export interface ObjCustomContent { html : string ; domNodes : any [ ] ; [ custom ] : any ; } export type CustomContent = ComponentChildren | ObjCustomContent ; export type CustomContentGenerator < HookProps > = CustomContent | ( ( hookProps : HookProps ) => CustomContent ) ; export type DefaultContentGenerator < HookProps > = ( hookProps : HookProps ) => ComponentChildren ; export const CustomContentRenderContext = createContext < number > ( 0 ) ; export interface ContentHookProps < HookProps > { hookProps : HookProps ; content : CustomContentGenerator < HookProps > ; defaultContent ? : DefaultContentGenerator < HookProps > ; children : ( innerElRef : Ref < any > , innerContent : ComponentChildren ) => ComponentChildren ; backupElRef ? : RefObject < any > ; } interface ContentHookInnerProps < HookProps > extends ContentHookProps < HookProps > { renderId : number ; } export function ContentHook < HookProps > ( props ) { return ( < CustomContentRenderContext . Consumer > { ( renderId ) => ( < ContentHookInner renderId = { renderId } { ... props } />)} < /CustomContentRenderContext.Consumer>); } class ContentHookInner < HookProps > extends BaseComponent < ContentHookInnerProps < HookProps > > { private innerElRef = createRef ( ) ; private customContentInfo : { contentKey : string ; contentVal : any ; render : ( el : HTMLElement , contentVal : any ) => void ; destroy ? : ( ) => void ; } ; render ( ) { return this . props . children ( this . innerElRef , this . renderInnerContent ( ) ) ; } componentDidMount ( ) { this . updateCustomContent ( ) ; } componentDidUpdate ( ) { this . updateCustomContent ( ) ; } componentWillUnmount ( ) { if ( this . customContentInfo && this . customContentInfo . destroy ) { this . customContentInfo . destroy ( ) ; } } private renderInnerContent ( ) { let { customContentInfo } = this ; let innerContent = this . getInnerContent ( ) ; let meta = this . getContentMeta ( innerContent ) ; if ( ! customContentInfo || customContentInfo . contentKey !== meta . contentKey ) { if ( customContentInfo ) { if ( customContentInfo . destroy ) { customContentInfo . destroy ( ) ; } customContentInfo = this . customContentInfo = null ; } if ( meta . contentKey ) { customContentInfo = this . customContentInfo = { contentKey : meta . contentKey , contentVal : innerContent [ meta . contentKey ] , ... meta . buildLifecycleFuncs ( ) , } ; } } else if ( customContentInfo ) { customContentInfo . contentVal = innerContent [ meta . contentKey ] ; } return customContentInfo ? [ ] : innerContent ; } private getInnerContent ( ) { let { props } = this ; let innerContent = normalizeContent ( props . content , props . hookProps ) ; if ( innerContent === undefined ) { innerContent = normalizeContent ( props . defaultContent , props . hookProps ) ; } return innerContent == null ? null : innerContent ; } private getContentMeta ( innerContent ) { let { contentTypeHandlers } = this . context . pluginHooks ; let contentKey = '' ; let buildLifecycleFuncs = null ; if ( innerContent ) { for ( let searchKey in contentTypeHandlers ) { if ( innerContent [ searchKey ] !== undefined ) { contentKey = searchKey ; buildLifecycleFuncs = contentTypeHandlers [ searchKey ] ; break ; } } } return { contentKey , buildLifecycleFuncs } ; } private updateCustomContent ( ) { if ( this . customContentInfo ) { this . customContentInfo . render ( this . innerElRef . current || this . props . backupElRef . current , this . customContentInfo . contentVal ) ; } } } export interface MountHookProps < ContentArg > { hookProps : ContentArg ; didMount : DidMountHandler < MountArg < ContentArg > > ; willUnmount : WillUnmountHandler < MountArg < ContentArg > > ; children : ( rootElRef : Ref < any > ) => ComponentChildren ; elRef ? : Ref < any > ; } export class MountHook < ContentArg > extends BaseComponent < MountHookProps < ContentArg > > { rootEl : HTMLElement ; render ( ) { return this . props . children ( this . handleRootEl ) ; } componentDidMount ( ) { let callback = this . props . didMount ; if ( callback ) { callback ( { ... this . props . hookProps , el : this . rootEl } ) ; } } componentWillUnmount ( ) { let callback = this . props . willUnmount ; if ( callback ) { callback ( { ... this . props . hookProps , el : this . rootEl } ) ; } } private handleRootEl = ( rootEl ) => { this . rootEl = rootEl ; if ( this . props . elRef ) { setRef ( this . props . elRef , rootEl ) ; } } ; } export function buildClassNameNormalizer < HookProps > ( ) { let currentGenerator ; let currentHookProps ; let currentClassNames = [ ] ; return ( generator , hookProps ) => { if ( ! currentHookProps || ! isPropsEqual ( currentHookProps , hookProps ) || generator !== currentGenerator ) { currentGenerator = generator ; currentHookProps = hookProps ; currentClassNames = normalizeClassNames ( generator , hookProps ) ; } return currentClassNames ; } ; } export type ClassNamesGenerator < HookProps > = ClassNamesInput | ( ( hookProps : HookProps ) => ClassNamesInput ) ; function normalizeClassNames < HookProps > ( classNames , hookProps ) { if ( typeof classNames === 'function' ) { classNames = classNames ( hookProps ) ; } return parseClassNames ( classNames ) ; } function normalizeContent ( input , hookProps ) { if ( typeof input === 'function' ) { return input ( hookProps , createElement ) ; } return input ; }