forked from jorgebucaran/hyperapp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.d.ts
238 lines (193 loc) · 8.2 KB
/
index.d.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
// 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
}