-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
combinator.ts
174 lines (139 loc) · 3.77 KB
/
combinator.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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import {debounce} from 'lodash-es'
import {Emitter} from './Emitter'
import {IconSequence} from './types'
/**
* Integrates multiple input events of the same type. The input event is triggered when any of the input events is triggered.
* @param bndrs Input events to combine.
* @returns A combined input event.
* @group Combinators
*/
export function combine<T>(...emitters: Emitter<T>[]): Emitter<T> {
if (emitters.length === 0) throw new Error('Zero-length emitters')
const ret = new Emitter({
sources: emitters,
})
const emit = debounce((value: T) => ret.emit(value), 0)
emitters.forEach(e => e.registerDerived(ret, emit))
ret.icon = emitters
.map(e => e.icon)
.filter((v: IconSequence | undefined): v is IconSequence => !!v)
.reduce((seq: IconSequence, icon) => {
if (seq.length === 0) {
return icon
} else {
return [...seq, ', ', ...icon]
}
}, [])
return ret
}
/**
* * Creates a cascading emitter by combining multiple emitters. The resulting emitter emits `true` when the given emitters emit truthy values in a sequential manner from the beginning to the end of the list.
* @param emitters Emitters to combine.
* @returns A cascading emitter.
* @group Combinators
*/
export function cascade(...emitters: Emitter[]): Emitter<boolean> {
const ret = new Emitter({
sources: emitters,
})
const values = emitters.map(e => !!e.value)
let prevCascadedIndex = -1
let cascadedIndex = -1
emitters.forEach((emitter, i) => {
emitter.registerDerived(ret, value => {
values[i] = !!value
if (value) {
if (cascadedIndex === i - 1) {
cascadedIndex = i
}
} else {
if (cascadedIndex === i) {
cascadedIndex = values.findIndex(v => v)
}
}
if (
prevCascadedIndex < cascadedIndex &&
cascadedIndex === emitters.length - 1
) {
ret.emit(true)
} else if (
cascadedIndex < prevCascadedIndex &&
cascadedIndex < emitters.length - 1
) {
ret.emit(false)
}
prevCascadedIndex = cascadedIndex
})
})
return ret
}
/**
* Creates a emitter that emits `true` when all of the given emitters emit truthy values.
* @param emitters Emitters to combine.
* @returns A new emitter
* @group Combinators
*/
export function and(...emitters: Emitter[]): Emitter<boolean> {
let prev = emitters.every(e => !!e.value)
const ret = new Emitter({
sources: emitters,
})
function handler() {
const value = emitters.every(e => !!e.value)
if (prev !== value) {
ret.emit(value)
}
prev = value
}
emitters.forEach(emitter => emitter.on(handler))
return ret
}
/**
* Creates a emitter that emits `true` when any of the given emitters emit truthy values.
* @param emitters Emitters to combine.
* @returns A new emitter
* @group Combinators
*/
export function or(...emitters: Emitter[]): Emitter<boolean> {
let prev = emitters.some(e => !!e.value)
const ret = new Emitter({
sources: emitters,
})
function handler() {
const value = emitters.some(e => !!e.value)
if (prev !== value) {
ret.emit(value)
}
prev = value
}
emitters.forEach(emitter => emitter.on(handler))
return ret
}
type UnwrapEmitter<T> = T extends Emitter<infer U> ? U : never
type UnwrapEmitters<T> = {
[K in keyof T]: UnwrapEmitter<T[K]>
}
/**
* Creates an input event with tuple type from given inputs.
* @returns An integrated input event with the tuple type of given input events.
* @group Combinators
*/
export function tuple<const T extends Emitter[]>(
...emitters: T
): Emitter<UnwrapEmitters<T>> {
let last = emitters.map(e => e.value)
const ret = new Emitter({
sources: emitters,
})
const emit = debounce(() => ret.emit(last), 0)
emitters.forEach((e, i) => {
e.registerDerived(ret, value => {
last = [...last]
last[i] = value
if (last.every(v => v !== undefined)) {
emit()
}
})
})
return ret
}