interactive.ts

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