| 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; |
| } |