// Minimum TypeScript Version: 4.2 // This requires every property of an object or none at all. type AllOrNothing = T | { [K in keyof T]?: never } // This ensures at least one property in an object is present. type AtLeastOne = { [K in keyof T]: Pick }[keyof T] // Credit: https://stackoverflow.com/a/59987826/1935675 // This ensures at least one object property group is present. type AtLeastSomething = U | AtLeastOne & AllOrNothing // Most event typings are provided by TypeScript itself. type EventsMap = & { [K in keyof HTMLElementEventMap as `on${K}`]: HTMLElementEventMap[K] } & { [K in keyof WindowEventMap as `on${K}`]: WindowEventMap[K] } & { onsearch: Event } // Indexable values are able to use subscripting. type Indexable = string | unknown[] | Record // This validates plain objects while invalidating array objects and string // objects by disallowing numerical indexing. type IndexableByKey = Record // Empty strings can cause issues in certain places. type NonEmptyString = T extends "" ? never : T // ----------------------------------------------------------------------------- declare module "hyperapp" { // `app()` initiates a Hyperapp instance. Only `app()`'s `node:` property and // effecters and subscribers are allowed to have side effects. function app(props: App): Dispatch // `h()` builds a virtual DOM node. function h( tag: NonEmptyString, props: CustomPayloads & Props, children?: MaybeVNode | readonly MaybeVNode[] ): ElementVNode // `memo()` stores a view along with any given data for it. function memo( view: (data: D) => VNode, data: D ): VNode // `text()` creates a virtual DOM node representing plain text. function text( // Values, aside from symbols and functions, can be handled. value: T extends symbol | ((..._: unknown[]) => unknown) ? never : T ): TextVNode // --------------------------------------------------------------------------- // This lets you make a variant of `h()` which is aware of your Hyperapp // instance's state. The `_ extends never` ensures that any state-aware // `h()` doesn't have an explicit state type that contradicts the // state type it actually uses. interface TypedH { <_ extends never, C = unknown, T extends string = string>( tag: NonEmptyString, props: CustomPayloads & Props, children?: MaybeVNode | readonly MaybeVNode[] ): ElementVNode } // --------------------------------------------------------------------------- // An action transforms existing state and/or wraps another action. type Action = (state: S, payload: P) => Dispatchable // A Hyperapp instance typically has an initial state and a top-level view // mounted over an available DOM element. type App = Readonly // The subscriptions function manages a set of subscriptions. subscriptions: (state: S) => readonly (boolean | undefined | Subscription)[] // Dispatching can be augmented to do custom processing. dispatch: (dispatch: Dispatch) => Dispatch }, { // The top-level view can build a virtual DOM node depending on the state. view: (state: S) => VNode // The mount node is where a Hyperapp instance will get placed. node: Node }>> // The `class` property represents an HTML class attribute string. type ClassProp = | boolean | string | undefined | Record | ClassProp[] // This lets event-handling actions properly accept custom payloads. type CustomPayloads = { [K in keyof T]?: K extends "style" ? StyleProp : T[K] extends [action: Action, payload: unknown] ? readonly [action: Action, payload: P] : T[K] } // Dispatching will cause state transitions. type Dispatch = (dispatchable: Dispatchable, payload?: unknown) => void // A dispatchable entity is used to cause a state transition. type Dispatchable = | S | [state: S, ...effects: MaybeEffect[]] | Action | readonly [action: Action, payload: P] // An effecter is the function that runs an effect. type Effecter = ( dispatch: Dispatch, payload: P ) => void | Promise // An effect is where side effects and any additional dispatching may occur. type Effect = | Effecter | readonly [effecter: Effecter, payload: P] // Effects can be declared conditionally. type MaybeEffect = null | undefined | boolean | "" | 0 | Effect // Event handlers are implemented using actions. type EventActions = { [K in keyof EventsMap]: | Action | readonly [action: Action, payload: unknown] } // In certain places a virtual DOM node can be made optional. type MaybeVNode = boolean | null | undefined | VNode // Virtual DOM properties will often correspond to HTML attributes. type Props = Readonly< Partial< Omit & ElementCreationOptions & EventActions > & { [_: string]: unknown class?: ClassProp key?: VNode["key"] style?: StyleProp // By disallowing `_VNode` we ensure values having the `VNode` type are // not mistaken for also having the `Props` type. _VNode?: never } > // The `style` property represents inline CSS. This relies on TypeScript's CSS // property definitions. Custom properties aren't covered as well as any newer // properties yet to be recognized by TypeScript. The only way to accommodate // them is to relax the adherence to TypeScript's CSS property definitions. // It's a poor trade-off given the likelihood of using such properties. // However, you can use type casting if you want to use them. type StyleProp = IndexableByKey & { [K in keyof CSSStyleDeclaration]?: CSSStyleDeclaration[K] | null } // A subscription reacts to external activity. type Subscription = readonly [ subscriber: (dispatch: Dispatch, payload: P) => Unsubscribe, payload: P ] // An unsubscribe function cleans up a canceled subscription. type Unsubscribe = () => void // A virtual DOM node (a.k.a. VNode) represents an actual DOM element. type ElementVNode = { readonly props: Props readonly children: readonly MaybeVNode[] node: null | undefined | Node // Hyperapp takes care of using native Web platform event handlers for us. events?: Record< string, Action | readonly [action: Action, payload: unknown] > // A key can uniquely associate a VNode with a certain DOM element. readonly key: string | null | undefined // A VNode's tag is either an element name or a memoized view function. readonly tag: string | ((data: Indexable) => VNode) // If the VNode's tag is a function then this data will get passed to it. memo?: Indexable // VNode types are based on actual DOM node types: // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType readonly type: 1 // `_VNode` is a phantom guard property which gives us a way to tell `VNode` // objects apart from `Props` objects. Since we don't expect users to make // their own VNodes manually, we can take advantage of this trick which // is unique to TypeScript type definitions for JavaScript code. _VNode: true } // Certain VNodes specifically represent Text nodes and don't rely on state. type TextVNode = { readonly props: {} readonly children: [] node: null | undefined | Node readonly key: undefined readonly tag: string readonly type: 3 _VNode: true } // VNodes may represent either Text or Element nodes. type VNode = ElementVNode | TextVNode }