interactive.ts

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