-
-
Notifications
You must be signed in to change notification settings - Fork 150
/
dropdown.ts
146 lines (141 loc) · 3.74 KB
/
dropdown.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import type { Fn } from "@thi.ng/api";
import { option, select, type SelectAttribs } from "@thi.ng/hiccup-html/forms";
import { $input } from "@thi.ng/rdom/event";
import { __nextID } from "@thi.ng/rdom/idgen";
import { $list } from "@thi.ng/rdom/list";
import { $replace } from "@thi.ng/rdom/replace";
import type { ISubscribable, ISubscription } from "@thi.ng/rstream";
export interface DropdownOpts<T> {
attribs: Partial<SelectAttribs>;
label: Fn<T, string>;
value: Fn<T, string>;
}
/**
* Dropdown `<select>` component with dynamically defined list of items (via
* subscription) and reactive updates using additionally provided `sel`
* subscription.
*
* @remarks
* Only single selections are supported. Each generated `<option>` element will
* have its own child subscription to update its `selected` attribute based on
* current value of `sel`.
*
* See {@link staticDropdown} or {@link staticDropdownAlt} for use cases where
* the items themselves are statically defined.
*
* @param items
* @param sel
* @param opts
*/
export const dynamicDropdown = <T = string, S extends string = string>(
items: ISubscribable<T[]>,
sel: ISubscription<S, S>,
opts?: Partial<DropdownOpts<T>>
) => {
opts = {
value: String,
label: String,
...opts,
};
return $list<T>(
items,
"select",
{ onchange: $input(sel), ...opts!.attribs },
$option(sel, <Required<DropdownOpts<T>>>opts)
);
};
/**
* Dropdown `<select>` component with statically defined list of items, but
* reactive updates using provided `sel` subscription.
*
* @remarks
* Only single selections are supported. Each generated `<option>` element will
* have its own child subscription to update its `selected` attribute based on
* current value of `sel`.
*
* See {@link staticDropdownAlt} for alternative approach or
* {@link dynamicDropdown} for use cases where the items themselves are
* dynamically changeable.
*
* @param items
* @param sel
* @param opts
* @returns
*/
export const staticDropdown = <T = string, S extends string = string>(
items: T[],
sel: ISubscription<S, S>,
opts?: Partial<DropdownOpts<T>>
) => {
opts = {
value: String,
label: String,
...opts,
};
return select(
{ onchange: $input(sel), ...opts.attribs },
...items.map($option(sel, <Required<DropdownOpts<T>>>opts))
);
};
/**
* Similar to {@link staticDropdown}, but using only a single subscription for
* the entire dropdown.
*
* @remarks
* **IMPORTANT:** This component is replacing its entire `<select>` element (and
* all its children) with each value change of `sel`. The component will only be
* fully mounted when `sel` produces a non-null value. In other words, `sel`
* **MUST** be pre-initialized for the component to show up (e.g. using
* rstream's
* [`reactive`](https://docs.thi.ng/umbrella/rstream/functions/reactive.html)
* with a seed value).
*
* Internally uses thi.ng/rdom
* [`$replace()`](https://docs.thi.ng/umbrella/rdom/functions/_replace.html).
*
* @param items
* @param sel
* @param opts
*/
export const staticDropdownAlt = <T = string, S extends string = string>(
items: T[],
sel: ISubscription<S, S>,
opts?: Partial<DropdownOpts<T>>
) => {
opts = {
value: String,
label: String,
...opts,
};
return $replace(
sel.map(
(id) =>
select(
{ onchange: $input(sel), ...opts!.attribs },
...items.map((x) => {
const value = opts!.value!(x);
return option(
{ value, selected: value === id },
opts!.label!(x)
);
})
),
{ id: __nextID("dropdown") }
)
);
};
const $option =
<T, S extends string>(
sel: ISubscription<S, S>,
{ label, value }: DropdownOpts<T>
) =>
(x: T) => {
let v = value(x);
return option(
{
value: v,
selected: sel.map((x) => v === x, { id: __nextID("opt") }),
},
label(x)
);
};