interactive.ts

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