hyperapp/index.d.ts

239 lines
8.2 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Minimum TypeScript Version: 4.2
// This requires every property of an object or none at all.
type AllOrNothing<T> = T | { [K in keyof T]?: never }
// This ensures at least one property in an object is present.
type AtLeastOne<T> = { [K in keyof T]: Pick<T, K> }[keyof T]
// Credit: https://stackoverflow.com/a/59987826/1935675
// This ensures at least one object property group is present.
type AtLeastSomething<T, U> = U | AtLeastOne<T> & AllOrNothing<U>
// 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<string, any>
// This validates plain objects while invalidating array objects and string
// objects by disallowing numerical indexing.
type IndexableByKey = Record<number, never>
// Empty strings can cause issues in certain places.
type NonEmptyString<T> = 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<S>(props: App<S>): Dispatch<S>
// `h()` builds a virtual DOM node.
function h<S, C = unknown, T extends string = string>(
tag: NonEmptyString<T>,
props: CustomPayloads<S, C> & Props<S>,
children?: MaybeVNode<S> | readonly MaybeVNode<S>[]
): ElementVNode<S>
// `memo()` stores a view along with any given data for it.
function memo<S, D extends Indexable = Indexable>(
view: (data: D) => VNode<S>,
data: D
): VNode<S>
// `text()` creates a virtual DOM node representing plain text.
function text<T = unknown>(
// 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<S> {
<_ extends never, C = unknown, T extends string = string>(
tag: NonEmptyString<T>,
props: CustomPayloads<S, C> & Props<S>,
children?: MaybeVNode<S> | readonly MaybeVNode<S>[]
): ElementVNode<S>
}
// ---------------------------------------------------------------------------
// An action transforms existing state and/or wraps another action.
type Action<S, P = any> = (state: S, payload: P) => Dispatchable<S>
// A Hyperapp instance typically has an initial state and a top-level view
// mounted over an available DOM element.
type App<S> =
Readonly<AtLeastSomething<{
// State is established through either direct assignment or an action.
init: Dispatchable<S>
// The subscriptions function manages a set of subscriptions.
subscriptions: (state: S) =>
readonly (boolean | undefined | Subscription<S>)[]
// Dispatching can be augmented to do custom processing.
dispatch: (dispatch: Dispatch<S>) => Dispatch<S>
}, {
// The top-level view can build a virtual DOM node depending on the state.
view: (state: S) => VNode<S>
// 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<string, boolean | undefined>
| ClassProp[]
// This lets event-handling actions properly accept custom payloads.
type CustomPayloads<S, T> = {
[K in keyof T]?:
K extends "style"
? StyleProp
: T[K] extends [action: Action<S, infer P>, payload: unknown]
? readonly [action: Action<S, P>, payload: P]
: T[K]
}
// Dispatching will cause state transitions.
type Dispatch<S> = (dispatchable: Dispatchable<S>, payload?: unknown) => void
// A dispatchable entity is used to cause a state transition.
type Dispatchable<S, P = any> =
| S
| [state: S, ...effects: MaybeEffect<S, P>[]]
| Action<S, P>
| readonly [action: Action<S, P>, payload: P]
// An effecter is the function that runs an effect.
type Effecter<S, P = any> = (
dispatch: Dispatch<S>,
payload: P
) => void | Promise<void>
// An effect is where side effects and any additional dispatching may occur.
type Effect<S, P = any> =
| Effecter<S, P>
| readonly [effecter: Effecter<S, P>, payload: P]
// Effects can be declared conditionally.
type MaybeEffect<S, P> = null | undefined | boolean | "" | 0 | Effect<S, P>
// Event handlers are implemented using actions.
type EventActions<S> = {
[K in keyof EventsMap]:
| Action<S, EventsMap[K]>
| readonly [action: Action<S>, payload: unknown]
}
// In certain places a virtual DOM node can be made optional.
type MaybeVNode<S> = boolean | null | undefined | VNode<S>
// Virtual DOM properties will often correspond to HTML attributes.
type Props<S> =
Readonly<
Partial<
Omit<HTMLElement, keyof (
DocumentAndElementEventHandlers &
ElementCSSInlineStyle &
GlobalEventHandlers
)> &
ElementCreationOptions &
EventActions<S>
> &
{
[_: string]: unknown
class?: ClassProp
key?: VNode<S>["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<S, P = any> = readonly [
subscriber: (dispatch: Dispatch<S>, 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<S> = {
readonly props: Props<S>
readonly children: readonly MaybeVNode<S>[]
node: null | undefined | Node
// Hyperapp takes care of using native Web platform event handlers for us.
events?:
Record<
string,
Action<S> | readonly [action: Action<S>, 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<S>)
// 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<S> = ElementVNode<S> | TextVNode
}