grainjs 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -71
- package/dist/cjs/index.js +5 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/lib/PriorityQueue.d.ts +1 -1
- package/dist/cjs/lib/_computed_queue.js +3 -3
- package/dist/cjs/lib/_computed_queue.js.map +1 -1
- package/dist/cjs/lib/binding.d.ts +11 -4
- package/dist/cjs/lib/binding.js +5 -5
- package/dist/cjs/lib/binding.js.map +1 -1
- package/dist/cjs/lib/computed.d.ts +49 -28
- package/dist/cjs/lib/computed.js +38 -52
- package/dist/cjs/lib/computed.js.map +1 -1
- package/dist/cjs/lib/dispose.d.ts +109 -96
- package/dist/cjs/lib/dispose.js +106 -80
- package/dist/cjs/lib/dispose.js.map +1 -1
- package/dist/cjs/lib/dom.d.ts +38 -18
- package/dist/cjs/lib/dom.js +44 -20
- package/dist/cjs/lib/dom.js.map +1 -1
- package/dist/cjs/lib/domComponent.d.ts +56 -48
- package/dist/cjs/lib/domComponent.js +66 -1
- package/dist/cjs/lib/domComponent.js.map +1 -1
- package/dist/cjs/lib/domComputed.d.ts +31 -21
- package/dist/cjs/lib/domComputed.js +14 -11
- package/dist/cjs/lib/domComputed.js.map +1 -1
- package/dist/cjs/lib/domDispose.d.ts +27 -12
- package/dist/cjs/lib/domDispose.js +26 -11
- package/dist/cjs/lib/domDispose.js.map +1 -1
- package/dist/cjs/lib/domForEach.d.ts +4 -3
- package/dist/cjs/lib/domForEach.js +10 -9
- package/dist/cjs/lib/domForEach.js.map +1 -1
- package/dist/cjs/lib/domImpl.d.ts +33 -10
- package/dist/cjs/lib/domImpl.js +28 -9
- package/dist/cjs/lib/domImpl.js.map +1 -1
- package/dist/cjs/lib/domMethods.d.ts +93 -47
- package/dist/cjs/lib/domMethods.js +88 -46
- package/dist/cjs/lib/domMethods.js.map +1 -1
- package/dist/cjs/lib/domevent.d.ts +87 -62
- package/dist/cjs/lib/domevent.js +84 -59
- package/dist/cjs/lib/domevent.js.map +1 -1
- package/dist/cjs/lib/emit.d.ts +62 -32
- package/dist/cjs/lib/emit.js +67 -53
- package/dist/cjs/lib/emit.js.map +1 -1
- package/dist/cjs/lib/kowrap.d.ts +6 -3
- package/dist/cjs/lib/kowrap.js +6 -3
- package/dist/cjs/lib/kowrap.js.map +1 -1
- package/dist/cjs/lib/obsArray.d.ts +91 -53
- package/dist/cjs/lib/obsArray.js +87 -55
- package/dist/cjs/lib/obsArray.js.map +1 -1
- package/dist/cjs/lib/observable.d.ts +25 -15
- package/dist/cjs/lib/observable.js +29 -18
- package/dist/cjs/lib/observable.js.map +1 -1
- package/dist/cjs/lib/pureComputed.d.ts +12 -15
- package/dist/cjs/lib/pureComputed.js +15 -18
- package/dist/cjs/lib/pureComputed.js.map +1 -1
- package/dist/cjs/lib/styled.d.ts +78 -61
- package/dist/cjs/lib/styled.js +26 -79
- package/dist/cjs/lib/styled.js.map +1 -1
- package/dist/cjs/lib/subscribe.d.ts +41 -37
- package/dist/cjs/lib/subscribe.js +31 -40
- package/dist/cjs/lib/subscribe.js.map +1 -1
- package/dist/cjs/lib/util.js +1 -0
- package/dist/cjs/lib/util.js.map +1 -1
- package/dist/cjs/lib/widgets/input.d.ts +3 -1
- package/dist/cjs/lib/widgets/input.js +6 -4
- package/dist/cjs/lib/widgets/input.js.map +1 -1
- package/dist/cjs/lib/widgets/select.d.ts +4 -2
- package/dist/cjs/lib/widgets/select.js +7 -5
- package/dist/cjs/lib/widgets/select.js.map +1 -1
- package/dist/esm/lib/_computed_queue.js +3 -3
- package/dist/esm/lib/_computed_queue.js.map +1 -1
- package/dist/esm/lib/binding.js +2 -2
- package/dist/esm/lib/binding.js.map +1 -1
- package/dist/esm/lib/computed.js +36 -50
- package/dist/esm/lib/computed.js.map +1 -1
- package/dist/esm/lib/dispose.js +104 -78
- package/dist/esm/lib/dispose.js.map +1 -1
- package/dist/esm/lib/dom.js +38 -18
- package/dist/esm/lib/dom.js.map +1 -1
- package/dist/esm/lib/domComponent.js +65 -0
- package/dist/esm/lib/domComponent.js.map +1 -1
- package/dist/esm/lib/domComputed.js +10 -7
- package/dist/esm/lib/domComputed.js.map +1 -1
- package/dist/esm/lib/domDispose.js +26 -11
- package/dist/esm/lib/domDispose.js.map +1 -1
- package/dist/esm/lib/domForEach.js +3 -2
- package/dist/esm/lib/domForEach.js.map +1 -1
- package/dist/esm/lib/domImpl.js +26 -7
- package/dist/esm/lib/domImpl.js.map +1 -1
- package/dist/esm/lib/domMethods.js +77 -35
- package/dist/esm/lib/domMethods.js.map +1 -1
- package/dist/esm/lib/domevent.js +84 -59
- package/dist/esm/lib/domevent.js.map +1 -1
- package/dist/esm/lib/emit.js +67 -53
- package/dist/esm/lib/emit.js.map +1 -1
- package/dist/esm/lib/kowrap.js +5 -2
- package/dist/esm/lib/kowrap.js.map +1 -1
- package/dist/esm/lib/obsArray.js +82 -50
- package/dist/esm/lib/obsArray.js.map +1 -1
- package/dist/esm/lib/observable.js +26 -15
- package/dist/esm/lib/observable.js.map +1 -1
- package/dist/esm/lib/pureComputed.js +15 -18
- package/dist/esm/lib/pureComputed.js.map +1 -1
- package/dist/esm/lib/styled.js +24 -77
- package/dist/esm/lib/styled.js.map +1 -1
- package/dist/esm/lib/subscribe.js +27 -36
- package/dist/esm/lib/subscribe.js.map +1 -1
- package/dist/esm/lib/util.js +1 -0
- package/dist/esm/lib/util.js.map +1 -1
- package/dist/esm/lib/widgets/input.js +3 -1
- package/dist/esm/lib/widgets/input.js.map +1 -1
- package/dist/esm/lib/widgets/select.js +3 -1
- package/dist/esm/lib/widgets/select.js.map +1 -1
- package/dist/grain-full.debug.js +2146 -3062
- package/dist/grain-full.debug.js.map +7 -0
- package/dist/grain-full.min.js +6 -2
- package/dist/grain-full.min.js.map +7 -1
- package/lib/binding.ts +9 -2
- package/lib/computed.ts +56 -56
- package/lib/dispose.ts +110 -85
- package/lib/dom.ts +39 -20
- package/lib/domComponent.ts +66 -57
- package/lib/domComputed.ts +29 -19
- package/lib/domDispose.ts +28 -11
- package/lib/domForEach.ts +7 -3
- package/lib/domImpl.ts +30 -7
- package/lib/domMethods.ts +101 -46
- package/lib/domevent.ts +85 -60
- package/lib/emit.ts +64 -50
- package/lib/kowrap.ts +5 -2
- package/lib/obsArray.ts +89 -54
- package/lib/observable.ts +26 -15
- package/lib/pureComputed.ts +16 -22
- package/lib/styled.ts +85 -71
- package/lib/subscribe.ts +41 -45
- package/lib/util.ts +1 -0
- package/lib/widgets/input.ts +3 -1
- package/lib/widgets/select.ts +3 -1
- package/package.json +38 -27
package/lib/domMethods.ts
CHANGED
|
@@ -12,9 +12,9 @@ import {G} from './browserGlobals';
|
|
|
12
12
|
const _dataMap: WeakMap<Node, {[key: string]: any}> = new WeakMap();
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Sets multiple attributes of a DOM element.
|
|
15
|
+
* Sets multiple attributes of a DOM element.
|
|
16
16
|
* Null and undefined values are omitted, and booleans are either omitted or set to empty string.
|
|
17
|
-
* @param
|
|
17
|
+
* @param attrsObj - Object mapping attribute names to attribute values.
|
|
18
18
|
*/
|
|
19
19
|
export function attrsElem(elem: Element, attrsObj: IAttrObj): void {
|
|
20
20
|
for (const key of Object.keys(attrsObj)) {
|
|
@@ -24,53 +24,74 @@ export function attrsElem(elem: Element, attrsObj: IAttrObj): void {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Sets multiple attributes of a DOM element. Null and undefined values are omitted, and booleans
|
|
30
|
+
* are either omitted or set to empty string.
|
|
31
|
+
*/
|
|
27
32
|
export function attrs(attrsObj: IAttrObj): DomElementMethod {
|
|
28
33
|
return (elem) => attrsElem(elem, attrsObj);
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
/**
|
|
32
37
|
* Sets an attribute of a DOM element to the given value. Removes the attribute when the value is
|
|
33
|
-
* null or undefined.
|
|
34
|
-
*
|
|
35
|
-
* @param
|
|
36
|
-
* @param
|
|
37
|
-
* @param {String|null} attrValue: The string value or null to remove the attribute.
|
|
38
|
+
* null or undefined.
|
|
39
|
+
* @param elem - The element to update.
|
|
40
|
+
* @param attrName - The name of the attribute to bind, e.g. 'href'.
|
|
41
|
+
* @param attrValue - The string value, or null or undefined to remove the attribute.
|
|
38
42
|
*/
|
|
39
|
-
export function attrElem(elem: Element, attrName: string, attrValue: string|null): void {
|
|
43
|
+
export function attrElem(elem: Element, attrName: string, attrValue: string|null|undefined): void {
|
|
40
44
|
if (attrValue === null || attrValue === undefined) {
|
|
41
45
|
elem.removeAttribute(attrName);
|
|
42
46
|
} else {
|
|
43
47
|
elem.setAttribute(attrName, attrValue);
|
|
44
48
|
}
|
|
45
49
|
}
|
|
46
|
-
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Sets an attribute of a DOM element to the given value. Removes the attribute when the value is
|
|
53
|
+
* null or undefined.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* dom('a', dom.attr('href', urlObs))
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export function attr(attrName: string, attrValueObs: BindableValue<string|null|undefined>): DomElementMethod {
|
|
47
61
|
return (elem) => _subscribe(elem, attrValueObs, (val) => attrElem(elem, attrName, val));
|
|
48
62
|
}
|
|
49
63
|
|
|
50
64
|
/**
|
|
51
65
|
* Sets or removes a boolean attribute of a DOM element. According to the spec, empty string is a
|
|
52
66
|
* valid true value for the attribute, and the false value is indicated by the attribute's absence.
|
|
53
|
-
*
|
|
54
|
-
* @param
|
|
55
|
-
* @param
|
|
56
|
-
* @param {Boolean} boolValue: Boolean value whether to set or unset the attribute.
|
|
67
|
+
* @param elem - The element to update.
|
|
68
|
+
* @param attrName - The name of the attribute to bind, e.g. 'checked'.
|
|
69
|
+
* @param boolValue - Boolean value whether to set or unset the attribute.
|
|
57
70
|
*/
|
|
58
71
|
export function boolAttrElem(elem: Element, attrName: string, boolValue: boolean): void {
|
|
59
72
|
attrElem(elem, attrName, boolValue ? '' : null);
|
|
60
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Dom-method that sets or removes a boolean attribute of a DOM element.
|
|
76
|
+
* @param attrName - The name of the attribute to bind, e.g. 'checked'.
|
|
77
|
+
* @param boolValueObs - Value, observable, or function for a whether to set or unset the attribute.
|
|
78
|
+
*/
|
|
61
79
|
export function boolAttr(attrName: string, boolValueObs: BindableValue<boolean>): DomElementMethod {
|
|
62
80
|
return (elem) => _subscribe(elem, boolValueObs, (val) => boolAttrElem(elem, attrName, val));
|
|
63
81
|
}
|
|
64
82
|
|
|
65
83
|
/**
|
|
66
|
-
* Adds a text node to the element.
|
|
67
|
-
*
|
|
68
|
-
* @param
|
|
69
|
-
* @param {String} value: The text value to add.
|
|
84
|
+
* Adds a text node to the element.
|
|
85
|
+
* @param elem - The element to update.
|
|
86
|
+
* @param value - The text value to add.
|
|
70
87
|
*/
|
|
71
88
|
export function textElem(elem: Node, value: string): void {
|
|
72
89
|
elem.appendChild(G.document.createTextNode(value));
|
|
73
90
|
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Sets text content of a DOM element to a value that may be an observable or a function.
|
|
94
|
+
*/
|
|
74
95
|
export function text(valueObs: BindableValue<string>): DomMethod {
|
|
75
96
|
return (elem) => {
|
|
76
97
|
const textNode = G.document.createTextNode('');
|
|
@@ -80,15 +101,21 @@ export function text(valueObs: BindableValue<string>): DomMethod {
|
|
|
80
101
|
}
|
|
81
102
|
|
|
82
103
|
/**
|
|
83
|
-
* Sets a style property of a DOM element to the given value.
|
|
84
|
-
*
|
|
85
|
-
* @param
|
|
86
|
-
* @param
|
|
87
|
-
* @param {String} value: The value for the property.
|
|
104
|
+
* Sets a style property of a DOM element to the given value.
|
|
105
|
+
* @param elem - The element to update.
|
|
106
|
+
* @param property - The name of the style property to update, e.g. 'fontWeight'.
|
|
107
|
+
* @param value - The value for the property.
|
|
88
108
|
*/
|
|
89
109
|
export function styleElem(elem: Element, property: string, value: string): void {
|
|
90
110
|
(elem as any).style[property] = value;
|
|
91
111
|
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Sets a style property of a DOM element to the given value, which may be an observable or a
|
|
115
|
+
* function.
|
|
116
|
+
* @param property - The name of the style property to update, e.g. 'fontWeight'.
|
|
117
|
+
* @param value - The value for the property.
|
|
118
|
+
*/
|
|
92
119
|
export function style(property: string, valueObs: BindableValue<string>): DomElementMethod {
|
|
93
120
|
return (elem) =>
|
|
94
121
|
_subscribe(elem, valueObs, (val) => styleElem(elem, property, val));
|
|
@@ -96,14 +123,20 @@ export function style(property: string, valueObs: BindableValue<string>): DomEle
|
|
|
96
123
|
|
|
97
124
|
/**
|
|
98
125
|
* Sets the property of a DOM element to the given value.
|
|
99
|
-
*
|
|
100
|
-
* @param
|
|
101
|
-
* @param
|
|
102
|
-
* @param {Object} value: The value for the property.
|
|
126
|
+
* @param elem - The element to update.
|
|
127
|
+
* @param property - The name of the property to update, e.g. 'disabled'.
|
|
128
|
+
* @param value - The value for the property.
|
|
103
129
|
*/
|
|
104
130
|
export function propElem<T>(elem: Node, property: string, value: T): void {
|
|
105
131
|
(elem as any)[property] = value;
|
|
106
132
|
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Sets the property of a DOM element to the given value, which may be an observable or a
|
|
136
|
+
* function.
|
|
137
|
+
* @param property - The name of the property to update, e.g. 'disabled'.
|
|
138
|
+
* @param value - The value for the property.
|
|
139
|
+
*/
|
|
107
140
|
export function prop<T>(property: string, valueObs: BindableValue<T>): DomMethod {
|
|
108
141
|
return (elem) => _subscribe(elem, valueObs, (val) => propElem(elem, property, val));
|
|
109
142
|
}
|
|
@@ -111,13 +144,17 @@ export function prop<T>(property: string, valueObs: BindableValue<T>): DomMethod
|
|
|
111
144
|
/**
|
|
112
145
|
* Shows or hides the element depending on a boolean value. Note that the element must be visible
|
|
113
146
|
* initially (i.e. unsetting style.display should show it).
|
|
114
|
-
*
|
|
115
|
-
* @param
|
|
116
|
-
* @param {Boolean} boolValue: True to show the element, false to hide it.
|
|
147
|
+
* @param elem - The element to update.
|
|
148
|
+
* @param boolValue - True to show the element, false to hide it.
|
|
117
149
|
*/
|
|
118
150
|
export function showElem(elem: HTMLElement, boolValue: boolean): void {
|
|
119
151
|
elem.style.display = boolValue ? '' : 'none';
|
|
120
152
|
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Shows or hides the element depending on a boolean value, which may be an observable or a function.
|
|
156
|
+
* Note that the element must be visible by default (i.e. unsetting `style.display` should show it).
|
|
157
|
+
*/
|
|
121
158
|
export function show(boolValueObs: BindableValue<boolean>): DomElementMethod {
|
|
122
159
|
return (elem) =>
|
|
123
160
|
_subscribe(elem, boolValueObs, (val) => showElem(elem, val));
|
|
@@ -125,13 +162,18 @@ export function show(boolValueObs: BindableValue<boolean>): DomElementMethod {
|
|
|
125
162
|
|
|
126
163
|
/**
|
|
127
164
|
* The opposite of show, hiding the element when boolValue is true.
|
|
128
|
-
*
|
|
129
|
-
* @param
|
|
130
|
-
* @param {Boolean} boolValue: True to hide the element, false to show it.
|
|
165
|
+
* @param elem - The element to update.
|
|
166
|
+
* @param boolValue - True to hide the element, false to show it.
|
|
131
167
|
*/
|
|
132
168
|
export function hideElem(elem: HTMLElement, boolValue: boolean): void {
|
|
133
169
|
elem.style.display = boolValue ? 'none' : '';
|
|
134
170
|
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* The opposite of show, hiding the element when boolValue is true. `boolValueObs` may be an
|
|
174
|
+
* observable or a function.
|
|
175
|
+
* Note that the element must be visible by default (i.e. unsetting `style.display` should show it).
|
|
176
|
+
*/
|
|
135
177
|
export function hide(boolValueObs: BindableValue<boolean>): DomElementMethod {
|
|
136
178
|
return (elem) =>
|
|
137
179
|
_subscribe(elem, boolValueObs, (val) => hideElem(elem, val));
|
|
@@ -148,12 +190,13 @@ export function clsElem(elem: Element, className: string, boolValue: boolean = t
|
|
|
148
190
|
* Sets or toggles a css class className. If className is an observable, it will be replaced when
|
|
149
191
|
* the observable changes. If a plain string, then an optional second boolean observable may be
|
|
150
192
|
* given, which will toggle it.
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
193
|
+
* ```ts
|
|
194
|
+
* dom.cls('foo') // Sets className 'foo'
|
|
195
|
+
* dom.cls('foo', isFoo); // Toggles 'foo' className according to observable.
|
|
196
|
+
* dom.cls('foo', (use) => use(isFoo)); // Toggles 'foo' className according to observable.
|
|
197
|
+
* dom.cls(fooClass); // Sets className to the value of fooClass observable
|
|
198
|
+
* dom.cls((use) => `prefix-${use(fooClass)}`); // Sets className to prefix- plus fooClass observable.
|
|
199
|
+
* ```
|
|
157
200
|
*/
|
|
158
201
|
export function cls(className: string, boolValue?: BindableValue<boolean>): DomElementMethod;
|
|
159
202
|
export function cls(className: BindableValue<string>): DomElementMethod;
|
|
@@ -193,11 +236,10 @@ function _clsDynamicPrefix(prefix: string, className: BindableValue<string>): Do
|
|
|
193
236
|
}
|
|
194
237
|
|
|
195
238
|
/**
|
|
196
|
-
* Associate arbitrary data with a DOM element.
|
|
197
|
-
*
|
|
198
|
-
* @param
|
|
199
|
-
* @param
|
|
200
|
-
* @param {Object} value: Arbitrary value to associate with elem.
|
|
239
|
+
* Associate arbitrary data with a DOM element.
|
|
240
|
+
* @param elem - The element with which to associate data.
|
|
241
|
+
* @param key - Key to identify this piece of data among others attached to elem.
|
|
242
|
+
* @param value - Arbitrary value to associate with elem.
|
|
201
243
|
*/
|
|
202
244
|
export function dataElem(elem: Node, key: string, value: any): void {
|
|
203
245
|
const obj = _dataMap.get(elem);
|
|
@@ -208,9 +250,19 @@ export function dataElem(elem: Node, key: string, value: any): void {
|
|
|
208
250
|
_dataMap.set(elem, {[key]: value});
|
|
209
251
|
}
|
|
210
252
|
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Associate arbitrary data with a DOM element: `value` may be an observable or a function.
|
|
256
|
+
* @param key - Key to identify this piece of data among others attached to elem.
|
|
257
|
+
* @param value - Arbitrary value to associate with elem.
|
|
258
|
+
*/
|
|
211
259
|
export function data(key: string, valueObs: BindableValue<any>): DomMethod {
|
|
212
260
|
return (elem) => _subscribe(elem, valueObs, (val) => dataElem(elem, key, val));
|
|
213
261
|
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Retrieve data associated with a DOM element using `data()` or `dataElem()`.
|
|
265
|
+
*/
|
|
214
266
|
export function getData(elem: Node, key: string) {
|
|
215
267
|
const obj = _dataMap.get(elem);
|
|
216
268
|
return obj && obj[key];
|
|
@@ -219,7 +271,7 @@ export function getData(elem: Node, key: string) {
|
|
|
219
271
|
/**
|
|
220
272
|
* A very simple setup to identify DOM elements for testing purposes. Here's the recommended
|
|
221
273
|
* usage.
|
|
222
|
-
*
|
|
274
|
+
* ```ts
|
|
223
275
|
* // In the component to be tested.
|
|
224
276
|
* import {noTestId, TestId} from 'grainjs';
|
|
225
277
|
*
|
|
@@ -228,17 +280,20 @@ export function getData(elem: Node, key: string) {
|
|
|
228
280
|
* dom(..., testId("another-name"), ...),
|
|
229
281
|
* );
|
|
230
282
|
* }
|
|
283
|
+
* ```
|
|
231
284
|
*
|
|
232
285
|
* In the fixture code using this component:
|
|
233
|
-
*
|
|
286
|
+
* ```ts
|
|
234
287
|
* import {makeTestId} from 'grainjs';
|
|
235
288
|
*
|
|
236
289
|
* dom(..., myComponent(myArgs, makeTestId('test-mycomp-'), ...)
|
|
290
|
+
* ```
|
|
237
291
|
*
|
|
238
292
|
* In the webdriver test code:
|
|
239
|
-
*
|
|
293
|
+
* ```ts
|
|
240
294
|
* driver.find('.test-my-comp-some-name')
|
|
241
295
|
* driver.find('.test-my-comp-another-name')
|
|
296
|
+
* ```
|
|
242
297
|
*
|
|
243
298
|
* When myComponent() is created with testId argument omitted, the testId() calls are no-ops. When
|
|
244
299
|
* makeTestId('test-foo-') is passed in, testId() calls simply add a css class with that prefix.
|
package/lib/domevent.ts
CHANGED
|
@@ -1,44 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* domevent provides a way to listen to DOM events, similar to JQuery's `on()` function. Its
|
|
3
|
-
* methods are also exposed via the dom.js module, as `dom.on()`, etc.
|
|
4
|
-
*
|
|
5
|
-
* It is typically used as an argument to the dom() function:
|
|
6
|
-
*
|
|
7
|
-
* dom('div', dom.on('click', (event, elem) => { ... }));
|
|
8
|
-
*
|
|
9
|
-
* When the div is disposed, the listener is automatically removed.
|
|
10
|
-
*
|
|
11
|
-
* The underlying interface to listen to an event is this:
|
|
12
|
-
*
|
|
13
|
-
* let listener = dom.onElem(elem, 'click', (event, elem) => { ... });
|
|
14
|
-
*
|
|
15
|
-
* The callback is called with the event and the element to which it was attached. Unlike in
|
|
16
|
-
* JQuery, the callback's return value is ignored. Use event.stopPropagation() and
|
|
17
|
-
* event.preventDefault() explicitly if needed.
|
|
18
|
-
*
|
|
19
|
-
* To stop listening:
|
|
20
|
-
*
|
|
21
|
-
* listener.dispose();
|
|
22
|
-
*
|
|
23
|
-
* Disposing the listener returned by .onElem() is the only way to stop listening to an event. You
|
|
24
|
-
* can use autoDispose to stop listening automatically when subscribing in a Disposable object:
|
|
25
|
-
*
|
|
26
|
-
* this.autoDispose(domevent.onElem(document, 'mouseup', callback));
|
|
27
|
-
*
|
|
28
|
-
* To listen to descendants of an element matching the given selector (what JQuery calls
|
|
29
|
-
* "delegated events", see http://api.jquery.com/on/):
|
|
30
|
-
*
|
|
31
|
-
* dom('div', dom.onMatch('.selector', 'click', (event, elem) => { ... }));
|
|
32
|
-
* or
|
|
33
|
-
* let lis = domevent.onMatchElem(elem, '.selector', 'click', (event, el) => { ... });
|
|
34
|
-
*
|
|
35
|
-
* In this usage, the element passed to the callback will be a DOM element matching the given
|
|
36
|
-
* selector. If there are multiple matches, the callback is only called for the innermost one.
|
|
37
|
-
*
|
|
38
|
-
* If you need to remove the callback on first call, here's a useful pattern:
|
|
39
|
-
* let lis = domevent.onElem(elem, 'mouseup', e => { lis.dispose(); other_work(); });
|
|
40
|
-
*/
|
|
41
|
-
|
|
42
1
|
import {IDisposable} from './dispose';
|
|
43
2
|
import {DomElementMethod, DomMethod} from './domImpl';
|
|
44
3
|
|
|
@@ -87,20 +46,59 @@ class DomEventMatchListener<E extends Event> extends DomEventListener<E, EventTa
|
|
|
87
46
|
}
|
|
88
47
|
|
|
89
48
|
/**
|
|
90
|
-
* Listen to a DOM event
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
49
|
+
* Listen to a DOM event, returning the listener object.
|
|
50
|
+
* ```ts
|
|
51
|
+
* const listener = dom.onElem(elem, 'click', (event, elem) => { ... });
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* To stop listening:
|
|
55
|
+
* ```ts
|
|
56
|
+
* listener.dispose();
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* Disposing the listener returned by `onElem()` is the only way to stop listening to an event. You
|
|
60
|
+
* can use `autoDispose` to stop listening automatically when subscribing in a `Disposable` object:
|
|
61
|
+
* ```ts
|
|
62
|
+
* this.autoDispose(domevent.onElem(document, 'mouseup', callback));
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* If you need "once" semantics, i.e. to remove the callback on first call, here's a useful pattern:
|
|
66
|
+
* ```ts
|
|
67
|
+
* const lis = domevent.onElem(elem, 'mouseup', e => { lis.dispose(); other_work(); });
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* @param elem - DOM Element to listen to.
|
|
71
|
+
* @param eventType - Event type to listen for (e.g. `'click'`).
|
|
72
|
+
* @param callback - Callback to call as `callback(event, elem)`, where elem is `elem`.
|
|
73
|
+
* @param options - `useCapture: boolean`: Add the listener in the capture phase. This should very
|
|
96
74
|
* rarely be useful (e.g. JQuery doesn't even offer it as an option).
|
|
97
|
-
* @returns
|
|
75
|
+
* @returns Listener object whose `.dispose()` method will remove the event listener.
|
|
98
76
|
*/
|
|
99
77
|
export function onElem<E extends EventName|string, T extends EventTarget>(
|
|
100
78
|
elem: T, eventType: E, callback: EventCB<EventType<E>, T>, {useCapture = false} = {}): IDisposable {
|
|
101
79
|
return new DomEventListener(elem, eventType, callback, useCapture);
|
|
102
80
|
}
|
|
103
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Listen to a DOM event. It is typically used as an argument to the `dom()` function:
|
|
84
|
+
* ```ts
|
|
85
|
+
* dom('div', dom.on('click', (event, elem) => { ... }));
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* When the div is disposed, the listener is automatically removed.
|
|
89
|
+
*
|
|
90
|
+
* The callback is called with the event and the element to which it was attached. Unlike in, say,
|
|
91
|
+
* JQuery, the callback's return value is ignored. Use `event.stopPropagation()` and
|
|
92
|
+
* `event.preventDefault()` explicitly if needed.
|
|
93
|
+
*
|
|
94
|
+
* To listen to descendants of an element matching the given selector (what JQuery calls
|
|
95
|
+
* "delegated events", see http://api.jquery.com/on/), see [`onMatch`](#onMatch).
|
|
96
|
+
*
|
|
97
|
+
* @param eventType - Event type to listen for (e.g. `'click'`).
|
|
98
|
+
* @param callback - Callback to call as `callback(event, elem)`, where `elem` is the element this
|
|
99
|
+
* listener is attached to.
|
|
100
|
+
* @param options - `useCapture?: boolean`: Add the listener in the capture phase.
|
|
101
|
+
*/
|
|
104
102
|
export function on<E extends EventName|string, T extends EventTarget>(
|
|
105
103
|
eventType: E, callback: EventCB<EventType<E>, T>, {useCapture = false} = {}): DomMethod<T> {
|
|
106
104
|
// tslint:disable-next-line:no-unused-expression
|
|
@@ -108,24 +106,46 @@ export function on<E extends EventName|string, T extends EventTarget>(
|
|
|
108
106
|
}
|
|
109
107
|
|
|
110
108
|
/**
|
|
111
|
-
* Listen to a DOM event on descendants of the given elem matching the given selector.
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
109
|
+
* Listen to a DOM event on descendants of the given elem matching the given selector.
|
|
110
|
+
*
|
|
111
|
+
* ```ts
|
|
112
|
+
* const let lis = domevent.onMatchElem(elem, '.selector', 'click', (event, el) => { ... });
|
|
113
|
+
* ```
|
|
114
|
+
*
|
|
115
|
+
* @param elem - DOM Element to whose descendants to listen.
|
|
116
|
+
* @param selector - CSS selector string to filter elements that trigger this event.
|
|
115
117
|
* JQuery calls it "delegated events" (http://api.jquery.com/on/). The callback will only be
|
|
116
118
|
* called when the event occurs for an element matching the given selector. If there are
|
|
117
119
|
* multiple elements matching the selector, the callback is only called for the innermost one.
|
|
118
|
-
* @param
|
|
119
|
-
* @param
|
|
120
|
+
* @param eventType - Event type to listen for (e.g. 'click').
|
|
121
|
+
* @param callback - Callback to call as `callback(event, elem)`, where elem is a
|
|
120
122
|
* descendent of `elem` which matches `selector`.
|
|
121
|
-
* @param
|
|
122
|
-
*
|
|
123
|
-
* @returns {Object} Listener object whose .dispose() method will remove the event listener.
|
|
123
|
+
* @param options - `useCapture?: boolean`: Add the listener in the capture phase.
|
|
124
|
+
* @returns Listener object whose `.dispose()` method will remove the event listener.
|
|
124
125
|
*/
|
|
125
126
|
export function onMatchElem(elem: EventTarget, selector: string, eventType: string,
|
|
126
127
|
callback: EventCB, {useCapture = false} = {}): IDisposable {
|
|
127
128
|
return new DomEventMatchListener(elem, eventType, callback, useCapture, selector);
|
|
128
129
|
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Listen to a DOM event on descendants of the given element matching the given selector.
|
|
133
|
+
*
|
|
134
|
+
* This is similar to JQuery's [delegated events](https://api.jquery.com/on/#direct-and-delegated-events)
|
|
135
|
+
*
|
|
136
|
+
* ```ts
|
|
137
|
+
* dom('div', dom.onMatch('.selector', 'click', (event, elem) => { ... }));
|
|
138
|
+
* ```
|
|
139
|
+
*
|
|
140
|
+
* In this usage, the element passed to the callback will be a DOM element matching the given
|
|
141
|
+
* selector. If there are multiple matches, the callback is only called for the innermost one.
|
|
142
|
+
*
|
|
143
|
+
* @param selector - CSS selector string to filter elements that trigger this event.
|
|
144
|
+
* @param eventType - Event type to listen for (e.g. `'click'`).
|
|
145
|
+
* @param callback - Callback to call as `callback(event, elem)`, where `elem` is an element
|
|
146
|
+
* matching `selector`.
|
|
147
|
+
* @param options - `useCapture?: boolean`: Add the listener in the capture phase.
|
|
148
|
+
*/
|
|
129
149
|
export function onMatch(selector: string, eventType: string, callback: EventCB,
|
|
130
150
|
{useCapture = false} = {}): DomElementMethod {
|
|
131
151
|
// tslint:disable-next-line:no-unused-expression
|
|
@@ -142,8 +162,6 @@ export interface IKeyHandlers<T extends HTMLElement = HTMLElement> {
|
|
|
142
162
|
* Listen to key events (typically 'keydown' or 'keypress'), with specified per-key callbacks.
|
|
143
163
|
* Key names are listed at https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
|
|
144
164
|
*
|
|
145
|
-
* Methods onKeyPress() and onKeyDown() are intended to be used as arguments to dom().
|
|
146
|
-
*
|
|
147
165
|
* By default, handled events are stopped from bubbling with stopPropagation() and
|
|
148
166
|
* preventDefault(). If, however, you register a key with a "$" suffix (i.e. "Enter$" instead of
|
|
149
167
|
* "Enter"), then the event is allowed to bubble normally.
|
|
@@ -152,7 +170,7 @@ export interface IKeyHandlers<T extends HTMLElement = HTMLElement> {
|
|
|
152
170
|
* to allow this element to receive keyboard events.
|
|
153
171
|
*
|
|
154
172
|
* For example:
|
|
155
|
-
*
|
|
173
|
+
* ```
|
|
156
174
|
* dom('input', ...
|
|
157
175
|
* dom.onKeyDown({
|
|
158
176
|
* Enter: (e, elem) => console.log("Enter pressed"),
|
|
@@ -160,6 +178,7 @@ export interface IKeyHandlers<T extends HTMLElement = HTMLElement> {
|
|
|
160
178
|
* Delete$: (e, elem) => console.log("Delete pressed, will bubble"),
|
|
161
179
|
* })
|
|
162
180
|
* )
|
|
181
|
+
* ```
|
|
163
182
|
*/
|
|
164
183
|
export function onKeyElem<T extends HTMLElement>(
|
|
165
184
|
elem: T, evType: KeyEventType, keyHandlers: IKeyHandlers<T>,
|
|
@@ -180,10 +199,16 @@ export function onKeyElem<T extends HTMLElement>(
|
|
|
180
199
|
});
|
|
181
200
|
}
|
|
182
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Add listeners to `"keypress"` events. See [`onKeyElem`](#onKeyElem) for details.
|
|
204
|
+
*/
|
|
183
205
|
export function onKeyPress<T extends HTMLElement>(keyHandlers: IKeyHandlers<T>): DomMethod<T> {
|
|
184
206
|
return (elem) => { onKeyElem(elem, 'keypress', keyHandlers); };
|
|
185
207
|
}
|
|
186
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Add listeners to `"keydown"` events. See [`onKeyElem`](#onKeyElem) for details.
|
|
211
|
+
*/
|
|
187
212
|
export function onKeyDown<T extends HTMLElement>(keyHandlers: IKeyHandlers<T>): DomMethod<T> {
|
|
188
213
|
return (elem) => { onKeyElem(elem, 'keydown', keyHandlers); };
|
|
189
214
|
}
|
package/lib/emit.ts
CHANGED
|
@@ -1,48 +1,16 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* emit.js implements an Emitter class which emits events to a list of listeners. Listeners are
|
|
3
|
-
* simply functions to call, and "emitting an event" just calls those functions.
|
|
4
|
-
*
|
|
5
|
-
* This is similar to Backbone events, with more focus on efficiency. Both inserting and removing
|
|
6
|
-
* listeners is constant time.
|
|
7
|
-
*
|
|
8
|
-
* To create an emitter:
|
|
9
|
-
* let emitter = new Emitter();
|
|
10
|
-
*
|
|
11
|
-
* To add a listener:
|
|
12
|
-
* let listener = fooEmitter.addListener(callback);
|
|
13
|
-
* To remove a listener:
|
|
14
|
-
* listener.dispose();
|
|
15
|
-
*
|
|
16
|
-
* The only way to remove a listener is to dispose the Listener object returned by addListener().
|
|
17
|
-
* You can often use autoDispose to do this automatically when subscribing in a constructor:
|
|
18
|
-
* this.autoDispose(fooEmitter.addListener(this.onFoo, this));
|
|
19
|
-
*
|
|
20
|
-
* To emit an event, call emit() with any number of arguments:
|
|
21
|
-
* emitter.emit("hello", "world");
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
// Note about a possible alternative implementation.
|
|
25
|
-
//
|
|
26
|
-
// We could implement the same interface using an array of listeners. Certain issues apply, in
|
|
27
|
-
// particular with removing listeners from inside emit(), and in ensuring that removals are
|
|
28
|
-
// constant time on average. Such an implementation was attempted and timed. The result is that
|
|
29
|
-
// compared to the linked-list implementation here, add/remove combination could be made nearly
|
|
30
|
-
// twice faster (on average), while emit and add/remove/emit are consistently slightly slower.
|
|
31
|
-
//
|
|
32
|
-
// The implementation here was chosen based on those timings, and as the simpler one. For example,
|
|
33
|
-
// on one setup (macbook, node4, 5-listener queue), add+remove take 0.1us, while add+remove+emit
|
|
34
|
-
// take 3.82us. (In array-based implementation with same set up, add+remove is 0.06us, while
|
|
35
|
-
// add+remove+emit is 4.80us.)
|
|
36
|
-
|
|
37
|
-
// The private property name to hold next/prev pointers.
|
|
38
|
-
|
|
39
1
|
function _noop() { /* noop */}
|
|
40
2
|
|
|
41
3
|
export type ListenerCB<T> = (this: T, ...args: any[]) => void;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A callback that listens to _changes_ in the Emitter listeners. This is mainly used for
|
|
7
|
+
* internal purposes.
|
|
8
|
+
*/
|
|
42
9
|
export type ChangeCB = (hasListeners: boolean) => void;
|
|
43
10
|
|
|
44
11
|
/**
|
|
45
12
|
* This is an implementation of a doubly-linked list, with just the minimal functionality we need.
|
|
13
|
+
* @internal
|
|
46
14
|
*/
|
|
47
15
|
export class LLink {
|
|
48
16
|
protected _next: LLink|null = null;
|
|
@@ -76,7 +44,7 @@ export class LLink {
|
|
|
76
44
|
}
|
|
77
45
|
|
|
78
46
|
protected _disposeList(): void {
|
|
79
|
-
let node: LLink = this;
|
|
47
|
+
let node: LLink = this; // eslint-disable-line @typescript-eslint/no-this-alias
|
|
80
48
|
let next = node._next;
|
|
81
49
|
while (next !== null) {
|
|
82
50
|
node._next = node._prev = null;
|
|
@@ -86,20 +54,63 @@ export class LLink {
|
|
|
86
54
|
}
|
|
87
55
|
}
|
|
88
56
|
|
|
57
|
+
/**
|
|
58
|
+
* An `Emitter` emits events to a list of listeners. Listeners are
|
|
59
|
+
* simply functions to call, and "emitting an event" just calls those functions.
|
|
60
|
+
*
|
|
61
|
+
* This is similar to Backbone events, with more focus on efficiency. Both inserting and removing
|
|
62
|
+
* listeners is constant time.
|
|
63
|
+
*
|
|
64
|
+
* To create an emitter:
|
|
65
|
+
* ```ts
|
|
66
|
+
* const emitter = new Emitter();
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* To add a listener:
|
|
70
|
+
* ```ts
|
|
71
|
+
* const listener = fooEmitter.addListener(callback);
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* To remove a listener:
|
|
75
|
+
* ```ts
|
|
76
|
+
* listener.dispose();
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* The only way to remove a listener is to dispose the `Listener` object returned by `addListener()`.
|
|
80
|
+
* You can often use autoDispose to do this automatically when subscribing in a constructor:
|
|
81
|
+
* ```ts
|
|
82
|
+
* this.autoDispose(fooEmitter.addListener(this.onFoo, this));
|
|
83
|
+
* ```
|
|
84
|
+
*
|
|
85
|
+
* To emit an event, call `emit()` with any number of arguments:
|
|
86
|
+
* ```ts
|
|
87
|
+
* emitter.emit("hello", "world");
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* @privateRemarks
|
|
91
|
+
*
|
|
92
|
+
* Note about a possible alternative implementation.
|
|
93
|
+
*
|
|
94
|
+
* We could implement the same interface using an array of listeners. Certain issues apply, in
|
|
95
|
+
* particular with removing listeners from inside emit(), and in ensuring that removals are
|
|
96
|
+
* constant time on average. Such an implementation was attempted and timed. The result is that
|
|
97
|
+
* compared to the linked-list implementation here, add/remove combination could be made nearly
|
|
98
|
+
* twice faster (on average), while emit and add/remove/emit are consistently slightly slower.
|
|
99
|
+
*
|
|
100
|
+
* The implementation here was chosen based on those timings, and as the simpler one. For example,
|
|
101
|
+
* on one setup (macbook, node4, 5-listener queue), add+remove take 0.1us, while add+remove+emit
|
|
102
|
+
* take 3.82us. (In array-based implementation with same set up, add+remove is 0.06us, while
|
|
103
|
+
* add+remove+emit is 4.80us.)
|
|
104
|
+
*/
|
|
89
105
|
export class Emitter extends LLink {
|
|
90
106
|
private _changeCB: ChangeCB = _noop;
|
|
91
107
|
private _changeCBContext: any = undefined;
|
|
92
108
|
|
|
93
|
-
/**
|
|
94
|
-
* Constructs an Emitter object.
|
|
95
|
-
*/
|
|
96
|
-
constructor() { super(); }
|
|
97
|
-
|
|
98
109
|
/**
|
|
99
110
|
* Adds a listening callback to the list of functions to call on emit().
|
|
100
|
-
* @param
|
|
101
|
-
* @param
|
|
102
|
-
* @returns
|
|
111
|
+
* @param callback - Function to call.
|
|
112
|
+
* @param optContext - Context for the function.
|
|
113
|
+
* @returns Listener object. Its dispose() method removes the callback from the list.
|
|
103
114
|
*/
|
|
104
115
|
public addListener<T>(callback: ListenerCB<T>, optContext?: T): Listener {
|
|
105
116
|
return new Listener(this, callback, optContext);
|
|
@@ -114,7 +125,7 @@ export class Emitter extends LLink {
|
|
|
114
125
|
|
|
115
126
|
/**
|
|
116
127
|
* Sets the single callback that would get called when a listener is added or removed.
|
|
117
|
-
* @param
|
|
128
|
+
* @param changeCB - Function to call after a listener is added or
|
|
118
129
|
* removed. It's called with a boolean indicating whether this Emitter has any listeners.
|
|
119
130
|
* Pass in `null` to unset the callback. Note that it can be called multiple times in a row
|
|
120
131
|
* with hasListeners `true`.
|
|
@@ -126,6 +137,7 @@ export class Emitter extends LLink {
|
|
|
126
137
|
|
|
127
138
|
/**
|
|
128
139
|
* Helper used by Listener class, but not intended for public usage.
|
|
140
|
+
* @internal
|
|
129
141
|
*/
|
|
130
142
|
public _triggerChangeCB(): void {
|
|
131
143
|
this._changeCB.call(this._changeCBContext, this.hasListeners());
|
|
@@ -150,10 +162,11 @@ export class Emitter extends LLink {
|
|
|
150
162
|
}
|
|
151
163
|
|
|
152
164
|
/**
|
|
153
|
-
* Listener object wraps a callback added to an Emitter, allowing for O(1) removal when the
|
|
154
|
-
* listener is disposed.
|
|
165
|
+
* The `Listener` object wraps a callback added to an Emitter, allowing for O(1) removal when the
|
|
166
|
+
* listener is disposed. It implements `IDisposable`.
|
|
155
167
|
*/
|
|
156
168
|
export class Listener extends LLink {
|
|
169
|
+
/** @internal */
|
|
157
170
|
public static callAll(begin: LLink, end: LLink, args: any[]): void {
|
|
158
171
|
while (begin !== end) {
|
|
159
172
|
const lis = begin as Listener;
|
|
@@ -170,6 +183,7 @@ export class Listener extends LLink {
|
|
|
170
183
|
emitter._triggerChangeCB();
|
|
171
184
|
}
|
|
172
185
|
|
|
186
|
+
/** @internal */
|
|
173
187
|
public dispose(): void {
|
|
174
188
|
if (this.isDisposed()) { return; }
|
|
175
189
|
this._removeNode(this);
|