1import merge from "ts-deepmerge"
2
3type InteractiveState = "default" | "hovered" | "clicked" | "selected" | "disabled";
4
5type Interactive<T> = {
6 default: T,
7 hovered?: T,
8 clicked?: T,
9 selected?: T,
10 disabled?: T,
11};
12
13interface InteractiveProps<T> {
14 base?: T,
15 state: Partial<Record<InteractiveState, T>>
16}
17
18/**
19 * Helper function for creating Interactive<T> objects that works pretty much like Toggle<T>.
20 * It takes a object to be used as a value for `default` field and then fills out other fields
21 * with fields from either `base` or `modifications`.
22 * Notably, it does not touch `hover`, `clicked` and `disabled` if there are no modifications for it.
23 *
24 * @param defaultObj Object to be used as the value for `default` field.
25 * @param base Object containing base fields to be included in the resulting object.
26 * @param modifications Object containing modified fields to be included in the resulting object.
27 * @returns Interactive<T> object with fields from `base` and `modifications`.
28 */
29export function interactive<T extends Object>({ base, state }: InteractiveProps<T>): Interactive<T> {
30 if (!base && !state.default) throw new Error("An interactive object must have a default state, or a base property.");
31
32 let defaultState: T;
33
34 if (state.default && base) {
35 defaultState = merge(base, state.default) as T;
36 } else {
37 defaultState = base ? base : state.default as T;
38 }
39
40 let interactiveObj: Interactive<T> = {
41 default: defaultState,
42 };
43
44 let stateCount = 0;
45
46 if (state.hovered !== undefined) {
47 interactiveObj.hovered = merge(interactiveObj.default, state.hovered) as T;
48 stateCount++;
49 }
50
51 if (state.clicked !== undefined) {
52 interactiveObj.clicked = merge(interactiveObj.default, state.clicked) as T;
53 stateCount++;
54 }
55
56 if (state.selected !== undefined) {
57 interactiveObj.selected = merge(interactiveObj.default, state.selected) as T;
58 stateCount++;
59 }
60
61 if (state.disabled !== undefined) {
62 interactiveObj.disabled = merge(interactiveObj.default, state.disabled) as T;
63 stateCount++;
64 }
65
66 if (stateCount < 1) {
67 throw new Error("An interactive object must have a default and at least one other state.");
68 }
69
70 return interactiveObj;
71}