import type { ArrowTemplate, ArrowTemplateKey } from './html' import { reactive } from './reactive' import type { Reactive, ReactiveTarget } from './reactive' export type Props = { [P in keyof T]: T[P] extends ReactiveTarget ? Props | T[P] : T[P] } export type EventMap = Record export type Events = { [K in keyof T]?: (payload: T[K]) => void } export type Emit = ( event: K, payload: T[K] ) => void type SyncFactory = | (() => ArrowTemplate) | ((props: Props) => ArrowTemplate) | ((props: Props, emit: Emit) => ArrowTemplate) | ((props: undefined, emit: Emit) => ArrowTemplate) type AsyncFactory = | (() => Promise | TValue) | ((props: Props) => Promise | TValue) | ((props: Props, emit: Emit) => Promise | TValue) | ((props: undefined, emit: Emit) => Promise | TValue) const AsyncFunction = (async () => {}).constructor as { new (...args: unknown[]): unknown } export type ComponentFactory = ( props?: Props, emit?: Emit ) => ArrowTemplate export interface AsyncComponentOptions< TProps extends ReactiveTarget, TValue, TEvents extends EventMap = EventMap, TSnapshot = TValue, > { fallback?: unknown onError?: ( error: unknown, props: Props, emit: Emit ) => unknown render?: ( value: TValue, props: Props, emit: Emit ) => unknown serialize?: ( value: TValue, props: Props, emit: Emit ) => TSnapshot deserialize?: (snapshot: TSnapshot, props: Props) => TValue idPrefix?: string } export type AsyncComponentInstaller = < TProps extends ReactiveTarget, TValue, TEvents extends EventMap = EventMap, TSnapshot = TValue, >( factory: AsyncFactory, options?: AsyncComponentOptions ) => Component | ComponentWithProps export interface ComponentCall { h: ComponentFactory p: Props | undefined e: Events | undefined k: ArrowTemplateKey key: (key: ArrowTemplateKey) => ComponentCall } export interface Component { (props?: undefined, events?: Events): ComponentCall } export interface ComponentWithProps< T extends ReactiveTarget, TEvents extends EventMap = EventMap, > { (props: S, events?: Events): ComponentCall } let asyncComponentInstaller: AsyncComponentInstaller | null = null type SourceBox = Reactive<{ 0: Props | undefined 1: ComponentFactory 2: Events | undefined }> function setComponentKey(this: ComponentCall, key: ArrowTemplateKey) { this.k = key return this } const propsProxyHandler: ProxyHandler = { get(target, key) { return target[0]?.[key as keyof (typeof target)[0]] }, has(target, key) { return key in (target[0] || {}) }, ownKeys(target) { return Reflect.ownKeys(target[0] || {}) }, getOwnPropertyDescriptor(target, key) { const source = target[0] return source && { configurable: true, enumerable: true, writable: true, value: (source as Record)[key as PropertyKey], } }, set(target, key, value) { return !!target[0] && Reflect.set(target[0] as object, key, value) }, } const narrowedPropsHandler: ProxyHandler<{ k: PropertyKey[] s: object }> = { get(target, key) { return target.k.includes(key) ? (target.s as Record)[key as PropertyKey] : undefined }, set(target, key, value) { if (!target.k.includes(key)) return false return Reflect.set(target.s, key, value) }, } export function pick( source: T, ...keys: K[] ): Pick export function pick( source: T ): T export function pick( source: T, ...keys: K[] ): T | Pick { return keys.length ? (new Proxy({ k: keys as PropertyKey[], s: source, }, narrowedPropsHandler) as unknown as Pick) : source } export function component(factory: () => ArrowTemplate): Component export function component( factory: (props: undefined, emit: Emit) => ArrowTemplate ): Component export function component( factory: (props: Props) => ArrowTemplate ): ComponentWithProps export function component( factory: (props: Props, emit: Emit) => ArrowTemplate ): ComponentWithProps export function component( factory: | (() => Promise | TValue) | ((props: undefined, emit: Emit) => Promise | TValue), options?: AsyncComponentOptions ): Component export function component< T extends ReactiveTarget, TValue, TEvents extends EventMap, TSnapshot = TValue, >( factory: | ((props: Props) => Promise | TValue) | ((props: Props, emit: Emit) => Promise | TValue), options?: AsyncComponentOptions ): ComponentWithProps export function component< T extends ReactiveTarget, TValue, TEvents extends EventMap = EventMap, TSnapshot = TValue, >( factory: SyncFactory | AsyncFactory, options?: AsyncComponentOptions ): Component | ComponentWithProps { if (options || factory.constructor === AsyncFunction) { if (!asyncComponentInstaller) { throw Error('Async runtime missing.') } return asyncComponentInstaller( factory as AsyncFactory, options ) as Component | ComponentWithProps } return ((input?: Props, events?: Events) => ({ h: factory as SyncFactory as ComponentFactory, k: undefined, p: input as Props | undefined, e: events as Events | undefined, key: setComponentKey, })) as Component | ComponentWithProps } export function installAsyncComponentInstaller( installer: AsyncComponentInstaller | null ) { asyncComponentInstaller = installer } export function isCmp(value: unknown): value is ComponentCall { return !!value && typeof value === 'object' && 'h' in value } export function createPropsProxy( source: Props | undefined, factory: ComponentFactory, events?: Events ): [Props, Emit, SourceBox] { const box = reactive({ 0: source, 1: factory, 2: events }) const emit = ((event: keyof EventMap, payload: unknown) => { const handler = box[2]?.[event] if (typeof handler === 'function') handler(payload) }) as Emit return [ new Proxy(box, propsProxyHandler) as unknown as Props, emit, box, ] }