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}