import { h , Component , ComponentConstructor , RenderableProps , ComponentChildren } from "preact" ; import { BitmaskFactory , createEmitter , ContextValueEmitter , noopEmitter } from "./context-value-emitter" ; import { getOnlyChildAndChildren } from "./utils" ; export interface ProviderProps < T > { value : T ; } export type ConsumerProps < T > = { unstable_observedBits ? : number ; } & ( { render : ( val : T ) => any ; } | { children : ( val : T ) => any ; } | { children : ComponentChildren ; } ) ; export type ConsumerState < T > = ProviderProps < T > ; export interface Context < T > { Provider : ComponentConstructor < ProviderProps < T > , { } > ; Consumer : ComponentConstructor < ConsumerProps < T > , ConsumerState < T > > ; } function getRenderer < T > ( props ) { const { child } = getOnlyChildAndChildren ( props ) ; return child || ( "render" in props && props . render ) ; } const MAX_SIGNED_31_BIT_INT = 1073741823 ; const defaultBitmaskFactory = ( ) => MAX_SIGNED_31_BIT_INT ; let ids = 0 ; function _createContext < T > ( value , bitmaskFactory ? ) { const key = `_preactContextProvider-${ ids ++ }` ; class Provider extends Component < ProviderProps < T > , any > { private _emitter : ContextValueEmitter < T > ; constructor ( props ) { super ( props ) ; this . _emitter = createEmitter ( props . value , bitmaskFactory || defaultBitmaskFactory ) ; } getChildContext ( ) { return { [ key ] : this . _emitter } ; } componentDidUpdate ( ) { this . _emitter . val ( this . props . value ) ; } render ( ) { const { child , children } = getOnlyChildAndChildren ( this . props ) ; if ( child ) { return child ; } return h ( "span" , null , children ) ; } } class Consumer extends Component < ConsumerProps < T > , ConsumerState < T > > { constructor ( props ? , ctx ? ) { super ( props , ctx ) ; this . state = { value : this . _getEmitter ( ) . val ( ) || value } ; } componentDidMount ( ) { this . _getEmitter ( ) . register ( this . _updateContext ) ; } shouldComponentUpdate ( nextProps , nextState ) { return ( this . state . value !== nextState . value || getRenderer ( this . props ) !== getRenderer ( nextProps ) ) ; } componentWillUnmount ( ) { this . _getEmitter ( ) . unregister ( this . _updateContext ) ; } componentDidUpdate ( _ , __ , prevCtx ) { const previousProvider = prevCtx [ key ] ; if ( previousProvider === this . context [ key ] ) { return ; } ( previousProvider || noopEmitter ) . unregister ( this . _updateContext ) ; this . componentDidMount ( ) ; } render ( ) { const render = "render" in this . props && this . props . render ; const r = getRenderer ( this . props ) ; if ( render && render !== r ) { console . warn ( "Both children and a render function are defined. Children will be used" ) ; } if ( typeof r === "function" ) { return r ( this . state . value ) ; } console . warn ( "Consumer is expecting a function as one and only child but didn't find any" ) ; } private _updateContext = ( value , bitmask ) => { const { unstable_observedBits } = this . props ; let observed = unstable_observedBits === undefined || unstable_observedBits === null ? MAX_SIGNED_31_BIT_INT : unstable_observedBits ; observed = observed | 0 ; if ( ( observed & bitmask ) === 0 ) { return ; } this . setState ( { value } ) ; } ; private _getEmitter ( ) { return this . context [ key ] || noopEmitter ; } } return { Provider : Provider as ComponentConstructor < ProviderProps < T > , { } > , Consumer : Consumer as ComponentConstructor < ConsumerProps < T > , ConsumerState < T > > } ; } export default _createContext ; export const createContext = _createContext ;