import { Component , ComponentChildren , FunctionalComponent } from "preact" ; /**
 * Make shallow differs between 2 objects.
 * Will check if properties have same references.
 */ export function shallowDiffers ( a , b ) { for ( let i in a ) if ( ! ( i in b ) ) return true ; for ( let i in b ) if ( a [ i ] !== b [ i ] ) return true ; return false ; } /**
 * Interface of the current hooked component.
 * The only important info here is that hooked component holds its own effects.
 * Effects (with useEffect) add themselves to the hooked component through
 * addEffect()
 */ interface IHookedComponent extends Component { addEffect ( effect ) ; } let hookedComponent ; /**
 * Get the current hooked component.
 * This will return the current component in factory phase.
 * There can be only one component in factory phase.
 * If this returns null, this means there is no component in factory phase and
 * you shouldn't call this from a hook, or a hook is used outside a factory phase.
 */ export function getHookedComponent ( ) { if ( process . env . NODE_ENV !== 'production' ) { ( hookedComponent == null ) && console . error ( `Prehook error // A hook is being used outside of a component's factory phase.` ) ; } return hookedComponent ; } /**
 * An effect holds 3 optional handlers to know when the component is mount,
 * updated or unmount.
 */ interface IEffect { mount ? : ( ) => void ; update ? : ( ) => void ; unmount ? : ( ) => void ; } /**
 * Type of the HOC's Factory.
 * A Factory function which returns a render function (which returns JSX DOM)
 */ type IFactory < GProps > = ( ) => ( ( ) => ComponentChildren ) ; param factory export function prehook < GProps = { } > ( factory ) { const name = factory . name || 'Component' ; return { [ name ] : function ( ) { let effects = [ ] ; /**
             * Name
             */ this . displayName = name ; this . toString = ( ) => `<${ name } ... />` ; /**
             * LifeCycle
             */ this . componentDidMount = ( ) => ( effects . forEach ( e => e . mount && e . mount ( ) ) ) ; this . componentDidUpdate = ( ) => ( effects . forEach ( e => e . update && e . update ( ) ) ) ; this . componentWillUnmount = ( ) => ( effects . forEach ( e => e . unmount && e . unmount ( ) ) ) ; this . shouldComponentUpdate = ( nextProps ) => ( shallowDiffers ( this . props , nextProps ) ) ; /**
             * Connecting hooks and init factory
             */ this . addEffect = ( effect ) => effects . push ( effect ) ; hookedComponent = this ; this . render = factory . call ( this ) ; hookedComponent = null ; /**
             * Render
             */ return this . render . apply ( this ) ; } } [ name ] ; } /**
 * To get current props and know when props are updated.
 */ interface IGetProps < GProps > { ( ) : GProps ; ( propName ? ) : ( ( ) => keyof GProps ) ; value ? : GProps ; } param defaultProps param actualProps returns { GProps } function injectDefault < GProps > ( defaultProps , actualProps ) { Object . keys ( defaultProps ) . map ( propKey => { if ( ! ( propKey in actualProps ) ) actualProps [ propKey ] = defaultProps [ propKey ] ; } ) ; return actualProps ; } /**
 * Use props to access to current hooked component passed properties.
 * -> const props = useProps();
 *
 * Set default properties by defining them as first argument
 * -> const props = useProps({
 *     title: 'Default title'
 * });
 *
 * Read current props's value by calling props function
 * -> props().title
 *
 * Read current props's alue by accessing with value property
 * -> props.value.title
 *
 * Create a prop watcher for useEffect by calling props with property key as
 * first argument
 * -> props('title') // will return () => props.value.title
 */ export function useProps < GProps > ( defaultProps ? ) { const component = getHookedComponent ( ) ; if ( process . env . NODE_ENV !== 'production' ) { ( hookedComponent . componentWillReceiveProps != null ) && console . error ( `Prehook error // useProps can only be used once by component.` ) ; } const getProps = ( propName ? ) => ( ( propName != null ) ? ( ) => getProps . value [ propName ] : getProps . value as any ) ; getProps . value = injectDefault < GProps > ( defaultProps , component . props ) ; component . componentWillReceiveProps = ( props ) => { getProps . value = injectDefault < GProps > ( defaultProps , props ) ; } ; return getProps ; } /**
 * Used state function interface returned by useState
 */ export interface IUsedState < GState > { ( ) : GState ; ( value ) : Promise < any > ; value : GState ; } /**
 * Create a state attached to the current hooked component.
 * -> const clickState = this.useState();
 *
 * Set the starting value as first argument
 * -> const clickState = this.useState( 0 );
 *
 * Read current state value by calling state function
 * -> clickState()
 *
 * Read current state value by accessing with value property
 * -> clickState.value.title
 *
 * Update current state value by passing the new value as first argument
 * -> clickState( 1 )
 *
 * Update current state value by passing function as first argument
 * -> clickState( current => current + 1 )
 *
 * Hooked component will be automatically updated and re-rendered after state
 * changes.
 *
 * A promise is returned when setting a new value. This promise is resolved when
 * component as rendered.
 */ export function useState < GState > ( state ) { const component = getHookedComponent ( ) ; const stateFactory = function ( value ? ) { if ( value == null ) return stateFactory . value ; if ( typeof value === 'function' ) value = value ( stateFactory . value ) ; stateFactory . value = value ; return new Promise ( resolve => component . forceUpdate ( resolve ) ) ; } ; stateFactory . value = state ; return stateFactory as IUsedState < GState > ; } /**
 * Type of mount handler.
 * Mount handler is function which optionally returns an unmount function.
 */ type IMountHandler = ( ) => ( ( ( ) => void ) | void ) ; /**
 * Watched states is a list of watcher.
 * A watcher is just a function returning the current value when called.
 * Directly states or watched props like so :
 * -> [ clickState, props('title') ]
 */ type IWatchedStates = ( ( ... rest ) => any ) [ ] ; /**
 * UseEffect to follow hooked component life cycle.
 *
 * Know when component has just rendered
 * -> useEffect( () => {
 *     // Component rendered (cDM, cDU)
 * });
 *
 * Know when component has just rendered and before update render
 * -> useEffect( () => {
 *     // Component rendered (cDM, cDU)
 *     return () => {
 *         // Before component rendering and after unmount
 *     }
 * });
 *
 * Know when component will mount and unmount
 * -> useEffect( false, () => {
 *     // Mount
 *     return () =>
 *     {
 *     	   // Unmount
 *     }
 * });
 *
 * Know when a state is changed and component rendered
 * -> useEffect( [ clickState ], () => {
 *     // State updated and component rendered
 *     // Also after first render
 *     return () => {
 *         // State changed but before next render
 *         // Also when component is unmount
 *     }
 * });
 *
 * Know when a prop is changed and component rendered
 * -> useEffect( [ props('title') ], () => {
 *     // Prop title updated and component rendered
 *     // Also after first render
 *     return () => {
 *         // Prop title changed but before next render
 *         // Also when component is unmount
 *     }
 * });
 *
 * You can mix-up properties and state watching by added them the first array.
 * This will act when any of the 2 will change.
 * -> useEffect( [ props('title'), clickState ], ... )
 *
 * If you do not need leaking scope of the mount handler to the unmount handler,
 * and/or if you need to distinguish mount from update from unmount.
 * All function are optional so you can only hook update if you want.
 * -> useEffect({
 *     mount	: () => { // called when component is mount }
 *     update	: () => { // called when component has updated }
 *     unmount	: () => { // called when component will unmount }
 * });
 */ export function useEffect ( statesOrEffect , mountHandler ? ) { const component = getHookedComponent ( ) ; const typeofFirst = typeof statesOrEffect ; const isArrayFirst = Array . isArray ( statesOrEffect ) ; if ( typeofFirst === 'object' && ! isArrayFirst ) { component . addEffect ( statesOrEffect as IEffect ) ; return ; } let states = statesOrEffect as IWatchedStates ; if ( mountHandler == null && typeofFirst === 'function' ) { mountHandler = ( statesOrEffect as IMountHandler ) ; states = null ; } let unmountHandler ; const unmount = ( ) => unmountHandler && unmountHandler ( ) ; const mount = ( ) => unmountHandler = mountHandler ( ) || null ; if ( ( typeofFirst === 'boolean' && ! statesOrEffect ) || ( isArrayFirst && ( statesOrEffect as IWatchedStates ) . length == 0 ) ) { component . addEffect ( { mount , unmount } ) ; return ; } const updateStates = ( ) => states . map ( state => state ( ) ) ; const hasStates = ! ! states ; let currentStates = hasStates && updateStates ( ) ; component . addEffect ( { mount , unmount , update : ( ) => { if ( hasStates ) { const newStates = updateStates ( ) ; const differences = newStates . filter ( ( state , i ) => state != currentStates [ i ] ) ; currentStates = newStates ; if ( differences . length == 0 ) return ; } unmount ( ) ; mount ( ) ; } } ) ; }