|
import type {ComponentChildren} from "preact"; |
|
import {Context, createContext as preactCreateContext, h, JSX} from "preact"; |
|
import {Dispatch, StateUpdater, useEffect, useRef, useState} from "preact/hooks"; |
|
|
|
|
|
type Actions = { |
|
[key: string]: (...rest: unknown[]) => unknown; |
|
}; |
|
|
|
type Conf<T, A extends Actions = never> = { |
|
initialValue: T | (() => T), |
|
controllers?: (value: T, setValue: Dispatch<StateUpdater<T>>) => { |
|
|
|
onMount?: () => void, |
|
|
|
|
|
effect?: () => void, |
|
|
|
actions?: A |
|
}, |
|
} |
|
|
|
type CustomContext<T, A> = Context<[ |
|
value: T, |
|
setter: Dispatch<StateUpdater<T>>, |
|
|
|
actions: A extends Actions ? A : never, |
|
...never[] |
|
]>; |
|
|
|
export function createContext<T, A>(conf: Conf<T, A>): CustomContext<T, A> { |
|
return preactCreateContext(conf) as CustomContext<T, A>; |
|
} |
|
|
|
export function ContextsProvider(props: { |
|
// contexts: Array<CustomContext<any, any, any>>, |
|
contexts: Array<CustomContext<unknown, unknown>>, |
|
children: ComponentChildren, |
|
}): JSX.Element | ComponentChildren { |
|
let node: JSX.Element | ComponentChildren = props.children; |
|
|
|
for (let i = props.contexts.length - 1; i >= 0; i--) { |
|
const context = props.contexts[i]; |
|
|
|
if (!context || typeof context !== "object" || !("__" in context)) { |
|
throw new Error("Invalid context provided. Ensure all contexts conform to CustomContext."); |
|
} |
|
|
|
const conf = context.__ as Conf<unknown, unknown>; |
|
|
|
|
|
const state = useState(conf.initialValue); |
|
let actions: Actions | undefined = undefined; |
|
|
|
if (conf.controllers) { |
|
|
|
const controller = conf.controllers(state[0], state[1]); |
|
|
|
if (controller.onMount) { |
|
useEffect(() => { |
|
controller.onMount!(); |
|
}, []) |
|
} |
|
|
|
if (controller.effect) { |
|
|
|
const effectRef = useRef(false); |
|
|
|
useEffect(() => { |
|
if (effectRef.current) return; |
|
effectRef.current = true; |
|
controller.effect!(); |
|
effectRef.current = false; |
|
}, [state[0]]) |
|
} |
|
|
|
|
|
|
|
if (controller.actions) { |
|
|
|
|
|
|
|
|
|
actions = controller.actions as Actions; |
|
} |
|
} |
|
|
|
|
|
node = h(context.Provider, {value: actions ? [...state, actions] : [...state]}, node) |
|
} |
|
|
|
return node; |
|
} |