2021-04-05 05:29:37 +02:00
|
|
|
|
// Minimum TypeScript Version: 4.2
|
|
|
|
|
|
2021-04-24 18:58:44 +02:00
|
|
|
|
// 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
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
|
2021-04-05 05:29:37 +02:00
|
|
|
|
declare module "hyperapp" {
|
2021-04-24 18:58:44 +02:00
|
|
|
|
// `app()` initiates a Hyperapp instance. Only `app()`'s `node:` property and
|
|
|
|
|
// effecters and subscribers are allowed to have side effects.
|
2021-04-05 05:29:37 +02:00
|
|
|
|
function app<S>(props: App<S>): Dispatch<S>
|
|
|
|
|
|
|
|
|
|
// `h()` builds a virtual DOM node.
|
2021-04-17 05:39:08 +02:00
|
|
|
|
function h<S, C = unknown, T extends string = string>(
|
2021-04-24 18:58:44 +02:00
|
|
|
|
tag: NonEmptyString<T>,
|
|
|
|
|
props: CustomPayloads<S, C> & Props<S>,
|
|
|
|
|
children?: MaybeVNode<S> | readonly MaybeVNode<S>[]
|
2021-05-05 02:38:07 +02:00
|
|
|
|
): ElementVNode<S>
|
2021-04-05 05:29:37 +02:00
|
|
|
|
|
|
|
|
|
// `memo()` stores a view along with any given data for it.
|
2021-04-24 18:58:44 +02:00
|
|
|
|
function memo<S, D extends Indexable = Indexable>(
|
|
|
|
|
view: (data: D) => VNode<S>,
|
|
|
|
|
data: D
|
|
|
|
|
): VNode<S>
|
2021-04-05 05:29:37 +02:00
|
|
|
|
|
|
|
|
|
// `text()` creates a virtual DOM node representing plain text.
|
2021-05-05 02:38:07 +02:00
|
|
|
|
function text<T = unknown>(
|
2021-04-24 18:58:44 +02:00
|
|
|
|
// Values, aside from symbols and functions, can be handled.
|
|
|
|
|
value: T extends symbol | ((..._: unknown[]) => unknown) ? never : T
|
2021-05-05 02:38:07 +02:00
|
|
|
|
): TextVNode
|
2021-04-05 05:29:37 +02:00
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
2021-04-24 18:58:44 +02:00
|
|
|
|
// 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.
|
2021-04-17 05:39:08 +02:00
|
|
|
|
interface TypedH<S> {
|
2021-04-24 18:58:44 +02:00
|
|
|
|
<_ extends never, C = unknown, T extends string = string>(
|
|
|
|
|
tag: NonEmptyString<T>,
|
|
|
|
|
props: CustomPayloads<S, C> & Props<S>,
|
|
|
|
|
children?: MaybeVNode<S> | readonly MaybeVNode<S>[]
|
2021-05-05 02:38:07 +02:00
|
|
|
|
): ElementVNode<S>
|
2021-04-17 05:39:08 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
2021-04-24 18:58:44 +02:00
|
|
|
|
// 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
|
2021-04-17 05:39:08 +02:00
|
|
|
|
// mounted over an available DOM element.
|
2021-04-24 18:58:44 +02:00
|
|
|
|
type App<S> =
|
|
|
|
|
Readonly<AtLeastSomething<{
|
|
|
|
|
// State is established through either direct assignment or an action.
|
|
|
|
|
init: Dispatchable<S>
|
2021-04-05 05:29:37 +02:00
|
|
|
|
|
2021-04-24 18:58:44 +02:00
|
|
|
|
// The subscriptions function manages a set of subscriptions.
|
|
|
|
|
subscriptions: (state: S) =>
|
|
|
|
|
readonly (boolean | undefined | Subscription<S>)[]
|
2021-04-05 05:29:37 +02:00
|
|
|
|
|
2021-04-24 18:58:44 +02:00
|
|
|
|
// 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>
|
2021-04-05 05:29:37 +02:00
|
|
|
|
|
2021-04-24 18:58:44 +02:00
|
|
|
|
// The mount node is where a Hyperapp instance will get placed.
|
|
|
|
|
node: Node
|
|
|
|
|
}>>
|
2021-04-05 05:29:37 +02:00
|
|
|
|
|
2021-04-24 18:58:44 +02:00
|
|
|
|
// The `class` property represents an HTML class attribute string.
|
|
|
|
|
type ClassProp =
|
|
|
|
|
| boolean
|
|
|
|
|
| string
|
|
|
|
|
| undefined
|
|
|
|
|
| Record<string, boolean | undefined>
|
|
|
|
|
| ClassProp[]
|
2021-04-05 05:29:37 +02:00
|
|
|
|
|
2021-04-24 18:58:44 +02:00
|
|
|
|
// 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
|
2021-04-05 05:29:37 +02:00
|
|
|
|
|
2021-04-24 18:58:44 +02:00
|
|
|
|
// A dispatchable entity is used to cause a state transition.
|
2021-04-17 05:39:08 +02:00
|
|
|
|
type Dispatchable<S, P = any> =
|
|
|
|
|
| S
|
2021-08-18 04:20:44 +02:00
|
|
|
|
| [state: S, ...effects: MaybeEffect<S, P>[]]
|
2021-04-17 05:39:08 +02:00
|
|
|
|
| Action<S, P>
|
2021-04-24 18:58:44 +02:00
|
|
|
|
| readonly [action: Action<S, P>, payload: P]
|
2021-04-05 05:29:37 +02:00
|
|
|
|
|
2022-03-02 13:25:20 +01:00
|
|
|
|
// An effecter is the function that runs an effect.
|
|
|
|
|
type Effecter<S, P = any> = (
|
|
|
|
|
dispatch: Dispatch<S>,
|
2021-04-17 05:39:08 +02:00
|
|
|
|
payload: P
|
2022-03-02 13:25:20 +01:00
|
|
|
|
) => 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]
|
|
|
|
|
|
2021-08-18 04:20:44 +02:00
|
|
|
|
|
|
|
|
|
// Effects can be declared conditionally.
|
|
|
|
|
type MaybeEffect<S, P> = null | undefined | boolean | "" | 0 | Effect<S, P>
|
|
|
|
|
|
2021-04-05 05:29:37 +02:00
|
|
|
|
|
2021-04-24 18:58:44 +02:00
|
|
|
|
// Event handlers are implemented using actions.
|
|
|
|
|
type EventActions<S> = {
|
|
|
|
|
[K in keyof EventsMap]:
|
|
|
|
|
| Action<S, EventsMap[K]>
|
|
|
|
|
| readonly [action: Action<S>, payload: unknown]
|
2021-04-05 11:35:20 +02:00
|
|
|
|
}
|
2021-04-05 05:29:37 +02:00
|
|
|
|
|
|
|
|
|
// In certain places a virtual DOM node can be made optional.
|
2021-04-24 18:58:44 +02:00
|
|
|
|
type MaybeVNode<S> = boolean | null | undefined | VNode<S>
|
2021-04-05 05:29:37 +02:00
|
|
|
|
|
|
|
|
|
// Virtual DOM properties will often correspond to HTML attributes.
|
2021-04-24 18:58:44 +02:00
|
|
|
|
type Props<S> =
|
2021-04-22 10:03:33 +02:00
|
|
|
|
Readonly<
|
|
|
|
|
Partial<
|
|
|
|
|
Omit<HTMLElement, keyof (
|
|
|
|
|
DocumentAndElementEventHandlers &
|
2021-04-18 02:40:33 +02:00
|
|
|
|
ElementCSSInlineStyle &
|
2021-04-22 10:03:33 +02:00
|
|
|
|
GlobalEventHandlers
|
|
|
|
|
)> &
|
|
|
|
|
ElementCreationOptions &
|
|
|
|
|
EventActions<S>
|
|
|
|
|
> &
|
|
|
|
|
{
|
2021-04-18 02:40:33 +02:00
|
|
|
|
[_: string]: unknown
|
|
|
|
|
class?: ClassProp
|
2021-04-24 18:58:44 +02:00
|
|
|
|
key?: VNode<S>["key"]
|
2021-04-18 02:40:33 +02:00
|
|
|
|
style?: StyleProp
|
|
|
|
|
|
2021-04-24 18:58:44 +02:00
|
|
|
|
// By disallowing `_VNode` we ensure values having the `VNode` type are
|
|
|
|
|
// not mistaken for also having the `Props` type.
|
|
|
|
|
_VNode?: never
|
2021-04-18 02:40:33 +02:00
|
|
|
|
}
|
2021-04-22 10:03:33 +02:00
|
|
|
|
>
|
2021-04-05 05:29:37 +02:00
|
|
|
|
|
2021-04-24 18:58:44 +02:00
|
|
|
|
// 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
|
|
|
|
|
}
|
2021-04-05 05:29:37 +02:00
|
|
|
|
|
2021-04-24 18:58:44 +02:00
|
|
|
|
// A subscription reacts to external activity.
|
|
|
|
|
type Subscription<S, P = any> = readonly [
|
|
|
|
|
subscriber: (dispatch: Dispatch<S>, payload: P) => Unsubscribe,
|
|
|
|
|
payload: P
|
|
|
|
|
]
|
2021-04-05 05:29:37 +02:00
|
|
|
|
|
2021-04-24 18:58:44 +02:00
|
|
|
|
// An unsubscribe function cleans up a canceled subscription.
|
|
|
|
|
type Unsubscribe = () => void
|
|
|
|
|
|
|
|
|
|
// A virtual DOM node (a.k.a. VNode) represents an actual DOM element.
|
2021-05-05 02:38:07 +02:00
|
|
|
|
type ElementVNode<S> = {
|
2021-04-24 18:58:44 +02:00
|
|
|
|
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
|
2021-04-17 05:39:08 +02:00
|
|
|
|
|
2021-04-24 18:58:44 +02:00
|
|
|
|
// 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
|
2021-05-05 02:38:07 +02:00
|
|
|
|
readonly type: 1
|
2021-04-24 18:58:44 +02:00
|
|
|
|
|
|
|
|
|
// `_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
|
|
|
|
|
}
|
2021-05-05 02:38:07 +02:00
|
|
|
|
|
|
|
|
|
// 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
|
2021-04-05 05:29:37 +02:00
|
|
|
|
}
|